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

Issueid #228824 Api integration of Fill in the blanks Mechanics #171

Conversation

ajinkyapandetekdi
Copy link
Collaborator

@ajinkyapandetekdi ajinkyapandetekdi commented Oct 16, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new interactive learning component, Mechanics2, enhancing user engagement with audio and fill-in-the-blank exercises.
    • Updated Mechanics3 component with improved layout and functionality for managing answers.
    • Integrated Mechanics6 into the practice view for better content delivery.
  • Bug Fixes

    • Enhanced audio playback functionality, ensuring smoother user experience.
  • Documentation

    • Updated PropTypes for the VoiceAnalyser component to improve clarity on required props.
    • Updated mechanism IDs for consistency in learning content.

Copy link

coderabbitai bot commented Oct 16, 2024

Walkthrough

The changes introduce a new interactive learning component, Mechanics2, in src/components/Practice/Mechanics6.jsx, enhancing the learning experience with audio and fill-in-the-blank exercises. The Mechanics3 component has been updated to include new props and state management features. Additionally, the Practice view has been modified to integrate the new Mechanics6 component. Updates have also been made to audio-related utilities to improve playback functionality and conditional rendering.

Changes

File(s) Change Summary
src/components/Practice/Mechanics3.jsx Updated to include new props options and audio. Introduced state variable answer for answer management. Added methods for handling answers and word removal.
src/components/Practice/Mechanics6.jsx New component added to manage interactive learning with hooks for state and audio playback.
src/views/Practice/Practice.jsx Modified to integrate Mechanics6 and updated rendering logic to conditionally display the new component based on the mechanism's name and ID.
src/utils/AudioCompare.js Updated rendering logic in AudioRecorder for audio playback buttons based on isShowCase prop. Adjusted margin-left style for Box component.
src/utils/VoiceAnalyser.js Enhanced audio playback functionality with checks to prevent playback under certain states. Updated playAudio and playRecordedAudio functions with new conditions.
Improved logic for audio source determination, allowing for an optional audioLink prop.
src/utils/constants.js Updated mechanism property in levelGetContent to change id field for "P5" and "P6" entries from "" to "mechanic_1".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PracticeView
    participant Mechanics3
    participant Mechanics6
    participant AudioCompare
    participant VoiceAnalyser

    User->>PracticeView: Interacts with practice
    PracticeView->>Mechanics3: Renders Mechanics3
    PracticeView->>Mechanics6: Renders Mechanics6
    Mechanics3->>VoiceAnalyser: Handles audio playback
    Mechanics6->>AudioCompare: Manages audio interactions
    VoiceAnalyser->>User: Provides audio feedback
    AudioCompare->>User: Displays audio controls
Loading

Possibly related PRs


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Outside diff range and nitpick comments (16)
src/utils/AudioCompare.js (2)

125-146: LGTM! Consider extracting the audio control rendering logic.

The changes improve the component's flexibility by introducing the isShowCase prop and enhance the user experience with conditional rendering of play/pause buttons. The code structure is clear and follows React best practices.

To further improve code readability, consider extracting the audio control rendering logic into a separate function:

const renderAudioControls = () => {
  if (props.pauseAudio) {
    return (
      <Box
        sx={{ cursor: "pointer" }}
        onClick={() => {
          props.playAudio(false);
        }}
      >
        <StopButton />
      </Box>
    );
  }
  return (
    <div
      onClick={() => {
        props.playAudio(true);
      }}
    >
      <Box sx={{ cursor: "pointer" }}>
        <ListenButton />
      </Box>
    </div>
  );
};

Then, you can use it in the JSX like this:

{!props.isShowCase && <Box>{renderAudioControls()}</Box>}

This refactoring would make the main render function more concise and easier to read.


151-151: Consider using undefined instead of an empty string for marginLeft.

The conditional styling for marginLeft is a good approach for dynamic layout adjustment. However, using an empty string as a fallback value might not be the best practice.

Consider modifying the line as follows:

marginLeft: props.isShowCase ? undefined : "35px",

Using undefined instead of an empty string will ensure that the marginLeft property is completely removed when isShowCase is true, rather than setting it to an empty string. This approach is more inline with React's style handling and can potentially avoid any unintended side effects.

src/utils/VoiceAnalyser.js (7)

94-102: Improved audio playback logic and error handling.

The changes to the playAudio function are well-implemented:

  1. The check for isStudentAudioPlaying prevents overlapping audio playback.
  2. Using the audioLink prop provides flexibility in audio source management.
  3. Error handling has been improved with more informative console messages and user alerts.

Consider using a more user-friendly error message in the alert, such as "Unable to play the audio. Please check your internet connection and try again."


122-124: Enhanced recorded audio playback control.

The changes to the playRecordedAudio function improve the audio playback control:

  1. The check for pauseAudio prevents unexpected behavior when audio is paused.
  2. Managing isStudentAudioPlaying state helps in coordinating different audio playback scenarios.

Consider adding a user-friendly error message when audio playback fails, similar to the improvement suggested for the playAudio function.


Line range hint 293-486: Comprehensive refactoring of fetchASROutput function.

The fetchASROutput function has undergone significant improvements:

  1. Enhanced error handling and data processing logic.
  2. Integration with AWS S3 for audio file storage.
  3. Handling of various scenarios like profanity filtering and updating learner profiles.

These changes make the function more robust and feature-rich.

Consider the following improvements:

  1. Extract the AWS S3 upload logic into a separate function for better modularity.
  2. Use async/await consistently throughout the function for better readability.
  3. Consider using a switch statement for handling different content types in the handlePercentageForLife function call.

Example refactoring for point 3:

switch (contentType.toLowerCase()) {
  case 'word':
    handlePercentageForLife(newThresholdPercentage, contentType, data?.subsessionFluency, lang);
    break;
  case 'sentence':
  case 'paragraph':
    // Add specific logic for sentence and paragraph if needed
    handlePercentageForLife(newThresholdPercentage, contentType, data?.subsessionFluency, lang);
    break;
  default:
    console.warn(`Unhandled content type: ${contentType}`);
}

Line range hint 488-586: Enhanced life calculation logic in handlePercentageForLife.

The handlePercentageForLife function has been significantly improved:

  1. More nuanced approach to managing lives based on performance and content type.
  2. Added audio feedback for life changes, enhancing user experience.

These changes provide a more engaging and responsive system for tracking user progress.

To improve maintainability and readability, consider the following refactoring suggestions:

  1. Extract the threshold calculation logic into a separate function.
  2. Use a switch statement or object lookup for content type-specific fluency criteria.
  3. Consider using constants for magic numbers (e.g., 5 for total lives).

Example refactoring:

const TOTAL_LIVES = 5;
const FLUENCY_CRITERIA = {
  word: 2,
  sentence: 6,
  paragraph: 10
};

function calculateThreshold(totalSyllables) {
  if (totalSyllables <= 100) return 30;
  if (totalSyllables <= 150) return 25;
  // ... other conditions
  return 5; // default for totalSyllables > 500
}

function meetsFluencyCriteria(contentType, fluencyScore) {
  const threshold = FLUENCY_CRITERIA[contentType.toLowerCase()] || 0;
  return fluencyScore < threshold;
}

// Use these functions in handlePercentageForLife

This refactoring will make the function easier to understand and maintain.


Line range hint 588-601: Updated getpermision function to use modern API.

The getpermision function has been improved by using the modern navigator.mediaDevices.getUserMedia API. This change enhances compatibility with current browsers and follows best practices.

Consider enhancing the error handling to provide more specific feedback:

const getpermision = () => {
  navigator.mediaDevices
    .getUserMedia({ audio: true })
    .then((stream) => {
      setAudioPermission(true);
    })
    .catch((error) => {
      console.error("Microphone permission denied:", error);
      setAudioPermission(false);
      if (error.name === 'NotAllowedError') {
        // Handle permission denied
      } else if (error.name === 'NotFoundError') {
        // Handle no microphone available
      } else {
        // Handle other errors
      }
    });
};

This will allow for more specific user feedback based on the type of error encountered.


629-629: Improved component flexibility and user communication.

The changes to the component's JSX enhance its functionality and user interaction:

  1. The new isShowCase prop allows for more flexible control of the AudioCompare component's behavior.
  2. The updated error message for blocked microphone provides clearer communication to the user.

Consider using a more descriptive variable name instead of isShowCase to better convey its purpose, such as isPreviewMode or isDemoMode.

Also applies to: 653-657


Line range hint 1-694: Overall improvements in audio handling, error management, and user experience.

The VoiceAnalyser component has undergone significant enhancements:

  1. Improved audio playback and recording logic with better state management.
  2. Enhanced error handling and user feedback throughout the component.
  3. Integration with AWS S3 for audio file storage.
  4. More nuanced life calculation based on user performance and content type.
  5. Updated to use modern browser APIs for microphone access.

These changes collectively improve the component's functionality, robustness, and user experience. The code structure and readability have been enhanced in most areas, though some functions (e.g., fetchASROutput and handlePercentageForLife) could benefit from further refactoring for improved maintainability.

Consider breaking down larger functions into smaller, more focused functions to improve maintainability and testability. This will make the component easier to understand and modify in the future.

src/components/Practice/Mechanics6.jsx (3)

56-56: Remove unnecessary commented-out code

The line // const [loading, setLoading] = useState(false); is commented out and no longer in use. To keep the code clean and readable, consider removing this unused code.


300-300: Remove unnecessary Fragment

The Fragment is unnecessary since it wraps a single element. Removing it can simplify the code.

Apply this diff to remove the Fragment:

- <>
    {isPlaying ? <StopAudioButton /> : <PlayAudioButton />}
  </>
+ {isPlaying ? <StopAudioButton /> : <PlayAudioButton />}
🧰 Tools
🪛 Biome

[error] 300-300: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


368-374: Simplify nested ternary operators for better readability

Nested ternary operators can make the code difficult to read and maintain. Consider refactoring the logic into variables or a helper function for clarity.

For example, you can refactor as:

let borderColor;
if (elem === "dash") {
  borderColor = selectedWord === wordToCheck ? "#58CC02" : "#C30303";
} else {
  borderColor = "#333F61";
}

// Then use borderColor in your style
border: `1px solid ${borderColor}`,
src/components/Practice/Mechanics3.jsx (3)

470-476: Simplify nested ternary operators for better readability

The nested ternary operators in your inline color styling make the code difficult to read and maintain.

Consider extracting the logic into a separate function or variable for clarity.

Example:

const getColor = () => {
  if (type === "audio" && selectedWord === elem) {
    return selectedWord === parentWords ? "#58CC02" : "#C30303";
  }
  return "#333F61";
};

And then use:

style={{ color: getColor(), /* other styles */ }}

489-536: Use unique keys when mapping over 'options'

Currently, you are using the index ind as the key when mapping over options. Using indices as keys can lead to issues when the list changes. It's better to use a unique identifier from the data item, such as elem.text or elem.id, if available.

Apply this diff to use elem.text as the key:

- key={ind}
+ key={elem.text}

195-200: Use 'const' instead of 'let' when the variable is not reassigned

In line 196, audio is declared with let, but it is not reassigned. Using const is preferred for variables that do not change.

Apply this diff:

- let audio = new Audio(removeSound);
+ const audio = new Audio(removeSound);
src/views/Practice/Practice.jsx (1)

Line range hint 771-775: Simplify the header assignment

Since mechanism.name is "fillInTheBlank" in this block, the condition mechanism.name === "fillInTheBlank" will always be true. You can simplify the header assignment by directly setting it to "Fill in the blank".

Apply this diff:

-                header:
-                  mechanism.name === "fillInTheBlank"
-                    ? "Fill in the blank"
-                    : questions[currentQuestion]?.contentType === "image"
-                    ? `Guess the below image`
-                    : `Speak the below ${questions[currentQuestion]?.contentType}`,
+                header: "Fill in the blank",
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 289b4e3 and e530ad8.

📒 Files selected for processing (5)
  • src/components/Practice/Mechanics3.jsx (10 hunks)
  • src/components/Practice/Mechanics6.jsx (1 hunks)
  • src/utils/AudioCompare.js (1 hunks)
  • src/utils/VoiceAnalyser.js (3 hunks)
  • src/views/Practice/Practice.jsx (10 hunks)
🧰 Additional context used
🪛 Biome
src/components/Practice/Mechanics6.jsx

[error] 147-147: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 183-183: 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)


[error] 300-300: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


[error] 435-458: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🔇 Additional comments (4)
src/components/Practice/Mechanics3.jsx (1)

523-526: ⚠️ Potential issue

Correct the color logic for 'fillInTheBlank' type

The current color logic does not account for the "fillInTheBlank" type, which may result in incorrect colors being applied.

Update the condition to include "fillInTheBlank".

Apply this diff:

Likely invalid or redundant comment.

src/views/Practice/Practice.jsx (3)

24-24: Import statement of Mechanics6 is correct

The Mechanics6 component is imported correctly from the specified path.


307-310: Conditional inclusion of mechanics_id in API call is correctly implemented

The mechanics_id parameter is appended to the API URL when currentGetContent.mechanism.id is available, ensuring that the API call includes this parameter only when necessary.


462-462: ⚠️ Potential issue

Potential undefined currentGetContent could cause errors

currentGetContent may be undefined if the find method does not return a matching element. Accessing currentGetContent.criteria without checking could result in a runtime error. Consider adding a check to ensure currentGetContent is defined before accessing its properties.

Apply this diff to add a null check:

+ if (currentGetContent) {
    setCurrentContentType(currentGetContent.criteria);
+ } else {
+   // Handle the undefined case appropriately
+   console.error('currentGetContent is undefined');
+ }

Likely invalid or redundant comment.

Comment on lines 1 to 518
</Box>
)}
</React.Fragment>
))}
</>
)}
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
marginTop: "20px",
marginBottom: "30px",
}}
>
{words?.map((elem) => (
<Box
className={`${
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? `audioSelectedWord`
: `audioSelectedWrongWord ${shake ? "shakeImage" : ""}`
: ""
}`}
onClick={() => handleWord(elem)}
sx={{
textAlign: "center",
px: "25px",
py: "12px",
// background: "transparent",
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
}}
>
<span
style={{
color:
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "32px",
fontFamily: "Quicksand",
}}
>
{elem}
</span>
</Box>
))}
</Box>
{
<Box sx={{ display: "flex", justifyContent: "center" }}>
<VoiceAnalyser
setVoiceText={setVoiceText}
setRecordedAudio={setRecordedAudio}
setVoiceAnimate={setVoiceAnimate}
storyLine={storyLine}
dontShowListen={true}
// updateStory={updateStory}
originalText={parentWords}
enableNext={getEnableButton()}
handleNext={handleNext}
{...{
contentId,
contentType,
currentLine: currentStep - 1,
playTeacherAudio,
callUpdateLearner,
isShowCase,
setEnableNext,
showOnlyListen: selectedWord != wordToCheck,
setOpenMessageDialog,
}}
/>
</Box>
}
{/* <Box
sx={{
display: "flex",
justifyContent: "center",
mt: "22px",
cursor: selectedWord ? "pointer" : "not-allowed",
}}
onClick={handleNext}
>
<SubmitButton opacity={selectedWord ? 1 : 0.3} />
</Box> */}
</MainLayout>
);
};

export default Mechanics2;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure the component name matches the filename

The component is named Mechanics2, but the file is named Mechanics6.jsx. For consistency and maintainability, consider renaming the component or the file so that they match.

🧰 Tools
🪛 Biome

[error] 147-147: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 183-183: 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)


[error] 300-300: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


[error] 435-458: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

Comment on lines +171 to +179
if (isPlaying) {
audioRef.current?.pause();
setIsPlaying(false);
} else {
audioRef.current.pause();
audioRef.current.load();
audioRef.current.play();
setIsPlaying(true);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Review the logic in togglePlayPause function

In the togglePlayPause function, when starting playback, the calls to pause() and load() before play() may not be necessary and could lead to unexpected behavior, such as resetting the playback position.

Consider simplifying the code:

if (isPlaying) {
  audioRef.current?.pause();
  setIsPlaying(false);
} else {
- audioRef.current.pause();
- audioRef.current.load();
  audioRef.current.play();
  setIsPlaying(true);
}

Unless there's a specific reason to pause and reload the audio, you can remove those calls to ensure smoother playback.

📝 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 (isPlaying) {
audioRef.current?.pause();
setIsPlaying(false);
} else {
audioRef.current.pause();
audioRef.current.load();
audioRef.current.play();
setIsPlaying(true);
}
if (isPlaying) {
audioRef.current?.pause();
setIsPlaying(false);
} else {
audioRef.current.play();
setIsPlaying(true);
}

}

// if (type === "audio") {
const isSoundCorrect = word === wordToCheck ? true : false;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Simplify boolean assignment in isSoundCorrect

The ternary operator is unnecessary here. You can directly assign the boolean expression to isSoundCorrect.

Apply this diff to simplify the code:

- const isSoundCorrect = word === wordToCheck ? true : false;
+ const isSoundCorrect = word === wordToCheck;
📝 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 isSoundCorrect = word === wordToCheck ? true : false;
const isSoundCorrect = word === wordToCheck;
🧰 Tools
🪛 Biome

[error] 147-147: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

};

const [currrentProgress, setCurrrentProgress] = React.useState(0);
const progressBarWidth = isNaN(currrentProgress / duration)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use Number.isNaN instead of isNaN for safer type checking

Using isNaN can lead to unexpected results due to type coercion. It's safer to use Number.isNaN for precise NaN checking.

Apply this diff to update the code:

- const progressBarWidth = isNaN(currrentProgress / duration)
+ const progressBarWidth = Number.isNaN(currrentProgress / duration)
    ? 0
    : currrentProgress / duration;

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 Biome

[error] 183-183: 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)

Comment on lines 435 to 458
<Box
className={`${
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? `audioSelectedWord`
: `audioSelectedWrongWord ${shake ? "shakeImage" : ""}`
: ""
}`}
onClick={() => handleWord(elem)}
sx={{
textAlign: "center",
px: "25px",
py: "12px",
// background: "transparent",
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
}}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add a unique key prop to items in the list

When rendering a list using .map(), each item should have a unique key prop to help React identify which items have changed.

Apply this diff to add the key prop:

{words?.map((elem) => (
+ <Box key={elem}
    className={`${
      type === "audio" && selectedWord === elem
        ? selectedWord === parentWords
          ? `audioSelectedWord`
          : `audioSelectedWrongWord ${shake ? "shakeImage" : ""}`
        : ""
    }`}
    onClick={() => handleWord(elem)}
    sx={{ /* styles */ }}
  >
    {/* content */}
  </Box>
))}

Ensure that elem is unique. If elem might not be unique, use an index or a unique identifier.

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 Biome

[error] 435-458: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

Comment on lines +540 to +543
`${process.env.REACT_APP_LEARNER_AI_APP_HOST}/${config.URLS.GET_CONTENT}/${currentGetContent.criteria}/${virtualId}?language=${lang}&contentlimit=${limit}&gettargetlimit=${limit}` +
(currentGetContent?.mechanism?.id
? `&mechanics_id=${currentGetContent?.mechanism?.id}`
: "")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor repeated API URL construction into a helper function

The code for constructing the API URL is duplicated in multiple places. Refactoring this into a helper function will reduce duplication and improve maintainability.

Example implementation:

const buildGetContentUrl = (currentGetContent, virtualId, lang, limit) => {
  let url = `${process.env.REACT_APP_LEARNER_AI_APP_HOST}/${config.URLS.GET_CONTENT}/${currentGetContent.criteria}/${virtualId}?language=${lang}&contentlimit=${limit}&gettargetlimit=${limit}`;
  if (currentGetContent?.mechanism?.id) {
    url += `&mechanics_id=${currentGetContent.mechanism.id}`;
  }
  return url;
};

Then replace the URL construction with:

const resGetContent = await axios.get(
  buildGetContentUrl(currentGetContent, virtualId, lang, limit)
);

Comment on lines +785 to +792
image: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
questions[currentQuestion]?.mechanics_data[0]?.image_url
: null,
audio: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
questions[currentQuestion]?.mechanics_data[0]?.audio_url
: null,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure safe access to mechanics_data[0] properties

When accessing mechanics_data[0], ensure that the array has at least one element to prevent potential runtime errors.

Modify the code to check that mechanics_data[0] exists:

const mechanicsDataItem = questions[currentQuestion]?.mechanics_data?.[0];
image: mechanicsDataItem
  ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` + mechanicsDataItem.image_url
  : null,
audio: mechanicsDataItem
  ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` + mechanicsDataItem.audio_url
  : null,

Comment on lines +814 to +816
options: questions[currentQuestion]?.mechanics_data
? questions[currentQuestion]?.mechanics_data[0]?.options
: null,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure safe access to mechanics_data[0].options

Similar to previous instances, ensure that mechanics_data[0] exists before accessing .options to prevent runtime errors.

Modify the code:

const mechanicsDataItem = questions[currentQuestion]?.mechanics_data?.[0];
options: mechanicsDataItem ? mechanicsDataItem.options : null,

Comment on lines +977 to +1037
} else if (
mechanism.name === "audio" ||
(mechanism.name === "fillInTheBlank" && mechanism.id === "")
) {
return (
<Mechanics6
page={page}
setPage={setPage}
{...{
level: !isShowCase && level,
header:
mechanism.name === "fillInTheBlank"
? "Fill in the blank"
: questions[currentQuestion]?.contentType === "image"
? `Guess the below image`
: `Speak the below ${questions[currentQuestion]?.contentType}`,
parentWords:
questions[currentQuestion]?.contentSourceData?.[0]?.text,
contentType: currentContentType,
contentId: questions[currentQuestion]?.contentId,
setVoiceText,
type: mechanism.name,
setRecordedAudio,
setVoiceAnimate,
storyLine,
handleNext,
image: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
questions[currentQuestion]?.mechanics_data[0]?.image_url
: null,
audio: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
questions[currentQuestion]?.mechanics_data[0]?.audio_url
: null,
enableNext,
showTimer: false,
points,
steps: questions?.length,
currentStep: currentQuestion + 1,
progressData,
showProgress: true,
background:
isShowCase &&
"linear-gradient(281.02deg, #AE92FF 31.45%, #555ADA 100%)",
playTeacherAudio,
callUpdateLearner: isShowCase,
disableScreen,
isShowCase,
handleBack: !isShowCase && handleBack,
setEnableNext,
allWords:
questions?.map((elem) => elem?.contentSourceData?.[0]?.text) ||
[],
loading,
setOpenMessageDialog,
options: questions[currentQuestion]?.mechanics_data
? questions[currentQuestion]?.mechanics_data[0]?.options
: null,
}}
/>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring renderMechanics to reduce code duplication

The renderMechanics function contains multiple if/else if blocks that render components with similar props, with slight variations. To improve maintainability and readability, consider refactoring the function to handle the different mechanisms in a more concise way.

You could create a mapping of mechanism names to components and their specific props, or extract common props into a base object and merge additional props based on the mechanism. This will reduce repetition and make future updates easier.

callUpdateLearner: isShowCase,
disableScreen,
isShowCase,
handleBack: !isShowCase && handleBack,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Passing false to handleBack prop may cause issues

When isShowCase is true, the expression !isShowCase && handleBack evaluates to false, resulting in false being passed to the handleBack prop. If the Mechanics6 component expects a function for handleBack, passing false could cause runtime errors.

Consider passing undefined or not including the handleBack prop when isShowCase is true:

handleBack: !isShowCase ? handleBack : undefined,

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (3)
src/components/Practice/Mechanics6.jsx (1)

504-514: Remove commented-out code

There's a block of commented-out code at the end of the component. If this code is no longer needed, it should be removed. Keeping commented-out code can lead to confusion and clutter in the codebase. If the code might be needed in the future, consider documenting the functionality elsewhere or relying on version control history.

src/components/Practice/Mechanics3.jsx (2)

1-1: LGTM! Consider adding prop types for better documentation.

The new imports and props (options and audio) are well-aligned with the new fill-in-the-blank functionality. Good job on modularizing the component by passing these as props.

Consider adding prop types (using PropTypes or TypeScript) for the new props to improve documentation and catch potential type-related bugs early.

Also applies to: 16-16, 52-53


63-68: LGTM! Consider renaming isAns for clarity.

The new answer state is well-structured to handle different types of answers for the fill-in-the-blank feature.

Consider renaming isAns to something more descriptive like isCorrect or isRightAnswer to improve code readability.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e530ad8 and 00d2c65.

📒 Files selected for processing (3)
  • src/components/Practice/Mechanics3.jsx (11 hunks)
  • src/components/Practice/Mechanics6.jsx (1 hunks)
  • src/utils/constants.js (2 hunks)
🧰 Additional context used
🪛 Biome
src/components/Practice/Mechanics6.jsx

[error] 183-183: 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)


[error] 300-300: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (5)
src/utils/constants.js (3)

3445-3445: Consistent update for P5 mechanism ID

The change in this hunk is consistent with the previous updates, setting the id to "mechanic_1" for the P5 level.


3406-3406: Verify integration with new Mechanics2 component

The consistent update of mechanism IDs for P5 and P6 levels might be related to the new Mechanics2 component mentioned in the AI summary.

To ensure proper integration, please run the following verification:

#!/bin/bash
# Description: Check the integration of the new mechanism ID with the Mechanics2 component

# Test 1: Look for usage of 'mechanic_1' in the Mechanics2 component
echo "Checking for 'mechanic_1' usage in Mechanics2 component:"
rg --type jsx 'mechanic_1' --glob 'src/components/Practice/Mechanics*.jsx'

# Test 2: Check if there are any references to the old empty mechanism ID
echo "Checking for references to empty mechanism ID:"
rg --type jsx 'mechanism.id === ""' --glob 'src/components/Practice/*.jsx'

Also applies to: 3412-3412, 3445-3445


3406-3406: Mechanism ID updated for P5 and P6 levels

The id field in the mechanism object has been updated from an empty string to "mechanic_1" for both P5 and P6 levels. This change appears to be intentional and consistent.

To ensure this change is applied consistently and doesn't introduce any unintended side effects, please run the following verification:

Also applies to: 3412-3412

✅ Verification successful

To proceed with the verification, please run the following updated scripts:


To address the issues found in the previous verification, please run the following updated scripts:


To resolve the regex error in Test 1, please run the following updated scripts:


To fix the regex errors and continue the verification, please run the following updated scripts:


Mechanism ID updates for P5 and P6 levels verified successfully

All P5 and P6 entries have the mechanism.id correctly set to "mechanic_1". No empty mechanism.id values or unintended references to "mechanic_1" were found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the consistency of mechanism ID changes and check for any potential impacts

# Test 1: Check if all P5 and P6 entries have the updated mechanism ID
echo "Checking P5 and P6 entries:"
rg --type js 'title: "P[56]"' -A 5 | rg 'mechanism:'

# Test 2: Search for any remaining empty mechanism IDs
echo "Checking for any remaining empty mechanism IDs:"
rg --type js 'mechanism: \{ id: ""'

# Test 3: Look for any direct references to 'mechanic_1' in other files
echo "Checking for direct references to 'mechanic_1':"
rg --type js 'mechanic_1' --glob '!src/utils/constants.js'

Length of output: 534


Script:

#!/bin/bash
# Description: Verify the consistency of mechanism ID changes and check for any potential impacts

# Test 1: Check if all P5 and P6 entries have the updated mechanism ID
echo "Checking P5 and P6 entries:"
rg --type js 'title: "P[56]"' -A 5 | rg 'mechanism:'

# Test 2: Search for any remaining empty mechanism IDs
echo "Checking for any remaining empty mechanism IDs:"
rg --type js 'mechanism: { id: ""'

# Test 3: Look for any direct references to 'mechanic_1' in other files
echo "Checking for direct references to 'mechanic_1':"
rg --type js 'mechanic_1' --glob '!src/utils/constants.js'

Length of output: 944


Script:

#!/bin/bash
# Description: Verify the consistency of mechanism ID changes and check for any potential impacts

# Test 1: Check if all P5 and P6 entries have the updated mechanism ID
echo "Checking P5 and P6 entries:"
rg --type js 'title: "P[56]"' -A 5 | rg 'mechanism: { id: "mechanic_1"'

# Test 2: Search for any remaining empty mechanism IDs
echo "Checking for any remaining empty mechanism IDs:"
rg --type js 'mechanism:\s*\{\s*id:\s*""\s*\}'

# Test 3: Look for any direct references to 'mechanic_1' in other files
echo "Checking for direct references to 'mechanic_1':"
rg --type js 'mechanic_1' --glob '!src/utils/constants.js'

Length of output: 587


Script:

#!/bin/bash
# Description: Verify the consistency of mechanism ID changes and check for any potential impacts

# Test 1: Check if all P5 and P6 entries have the updated mechanism ID
echo "Checking P5 and P6 entries:"
rg --type js 'title: "P[56]"' -A 5 | rg 'mechanism: { id: "mechanic_1" }'

# Test 2: Search for any remaining empty mechanism IDs
echo "Checking for any remaining empty mechanism IDs:"
rg --type js 'mechanism:\s*\{\s*id:\s*""\s*\}'

# Test 3: Look for any direct references to 'mechanic_1' in other files
echo "Checking for direct references to 'mechanic_1':"
rg --type js 'mechanic_1' --glob '!src/utils/constants.js'

Length of output: 591


Script:

#!/bin/bash
# Description: Verify the consistency of mechanism ID changes and check for any potential impacts

# Test 1: Check if all P5 and P6 entries have the updated mechanism ID
echo "Checking P5 and P6 entries:"
rg --type js 'title: "P[56]"' -A 5 | rg 'mechanism:\s*\{\s*id:\s*"mechanic_1"\s*\}'

# Test 2: Search for any remaining empty mechanism IDs
echo "Checking for any remaining empty mechanism IDs:"
rg --type js 'mechanism:\s*\{\s*id:\s*""\s*\}'

# Test 3: Look for any direct references to 'mechanic_1' in other files
echo "Checking for direct references to 'mechanic_1':"
rg --type js 'mechanic_1' --glob '!src/utils/constants.js'

Length of output: 474

src/components/Practice/Mechanics3.jsx (2)

195-200: LGTM! handleRemoveWord function is well-implemented.

The handleRemoveWord function correctly handles the removal of a selected word, including resetting the answer state, playing a sound, and disabling the next button.


Line range hint 551-561: LGTM! VoiceAnalyser props updated appropriately.

The new props added to the VoiceAnalyser component (audioLink, showOnlyListen, and setOpenMessageDialog) are well-integrated and seem to support the new audio and UI control functionalities.

Comment on lines +1 to +51
import { Box, Typography } from "@mui/material";
import HomophonesFinder from "homophones";
import React, { createRef, useEffect, useState } from "react";
import {
AudioBarColoredSvg,
AudioBarSvg,
AudioPlayerSvg,
PlayAudioButton,
StopAudioButton,
getLocalData,
randomizeArray,
} from "../../utils/constants";
import MainLayout from "../Layouts.jsx/MainLayout";
import correctSound from "../../assets/audio/correct.wav";
import wrongSound from "../../assets/audio/wrong.wav";
import VoiceAnalyser from "../../utils/VoiceAnalyser";

const Mechanics2 = ({
page,
setPage,
type,
handleNext,
background,
header,
parentWords,
image,
setVoiceText,
setRecordedAudio,
setVoiceAnimate,
storyLine,
enableNext,
showTimer,
points,
steps,
currentStep,
contentId,
contentType,
level,
isDiscover,
progressData,
showProgress,
playTeacherAudio = () => {},
callUpdateLearner,
disableScreen,
isShowCase,
handleBack,
allWords,
setEnableNext,
loading,
setOpenMessageDialog,
}) => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider renaming the component or the file for consistency

The component is named Mechanics2, but the file is named Mechanics6.jsx. This mismatch could lead to confusion and maintenance issues. Consider renaming either the component or the file to ensure consistency.

Additionally, the component accepts a large number of props. Consider grouping related props into objects or splitting the component into smaller, more focused components to improve maintainability and readability.

Comment on lines +52 to +95
const [words, setWords] = useState([]);
const [sentences, setSentences] = useState([]);

const [selectedWord, setSelectedWord] = useState("");
// const [loading, setLoading] = useState(false);
const [shake, setShake] = useState(false);
const [wordToFill, setWordToFill] = useState("");
const [disabledWords, setDisabledWords] = useState(false);
const lang = getLocalData("lang");
let wordToCheck = type === "audio" ? parentWords : wordToFill;

useEffect(() => {
const initializeFillInTheBlank = async () => {
if (type === "fillInTheBlank" && parentWords?.length) {
let wordsArr = parentWords.split(" ");
let randomIndex = Math.floor(Math.random() * wordsArr.length);
try {
await getSimilarWords(wordsArr[randomIndex]);
setWordToFill(wordsArr[randomIndex]);
wordsArr[randomIndex] = "dash";
setSentences(wordsArr);
setSelectedWord("");
} catch (error) {
console.error("Error in initializeFillInTheBlank:", error);
}
}
};
initializeFillInTheBlank();
}, [contentId, parentWords]);

useEffect(() => {
const initializeAudio = async () => {
if (type === "audio" && parentWords) {
setDisabledWords(true);
setSelectedWord("");
try {
await getSimilarWords(parentWords);
} catch (error) {
console.error("Error in initializeAudio:", error);
}
}
};
initializeAudio();
}, [contentId, parentWords]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize useEffect hooks

The two useEffect hooks have similar functionality and dependencies. Consider combining them into a single hook to reduce code duplication and improve maintainability. You could use a conditional check inside the hook to differentiate between "audio" and "fillInTheBlank" types.

Example:

useEffect(() => {
  const initializeExercise = async () => {
    if (type === "fillInTheBlank" && parentWords?.length) {
      // Fill in the blank initialization logic
    } else if (type === "audio" && parentWords) {
      // Audio initialization logic
    }
  };
  initializeExercise();
}, [contentId, parentWords, type]);

This approach would consolidate the initialization logic and potentially reduce the number of re-renders.

Comment on lines +97 to +125
const getSimilarWords = async (wordForFindingHomophones) => {
const lang = getLocalData("lang");
// const isFillInTheBlanks = type === "fillInTheBlank";
const wordToSimilar = wordForFindingHomophones
? wordForFindingHomophones
: parentWords;

if (lang === "en") {
const finder = new HomophonesFinder();
const homophones = await finder.find(wordToSimilar);
let wordsArr = [homophones[8], wordToSimilar, homophones[6]];
setWords(randomizeArray(wordsArr));
} else {
let wordsToShow = [];
if (type == "audio") {
wordsToShow = allWords?.filter((elem) => elem != wordToSimilar);
}
if (type == "fillInTheBlank") {
wordsToShow = allWords
?.join(" ")
?.split(" ")
.filter((elem) => elem !== wordToSimilar && elem.length > 2);
}

wordsToShow = randomizeArray(wordsToShow).slice(0, 2);
wordsToShow.push(wordToSimilar);
setWords(randomizeArray(wordsToShow));
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in getSimilarWords

The getSimilarWords function doesn't have any error handling for potential issues with the HomophonesFinder or array operations. Consider adding try-catch blocks to handle potential errors gracefully.

Example:

const getSimilarWords = async (wordForFindingHomophones) => {
  try {
    // Existing logic
  } catch (error) {
    console.error("Error in getSimilarWords:", error);
    // Handle the error appropriately, e.g., set a default value for words
  }
};

This will help prevent unhandled promise rejections and improve the robustness of the component.

Comment on lines +170 to +180
const togglePlayPause = () => {
if (isPlaying) {
audioRef.current?.pause();
setIsPlaying(false);
} else {
audioRef.current.pause();
audioRef.current.load();
audioRef.current.play();
setIsPlaying(true);
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify togglePlayPause function

The togglePlayPause function can be simplified by removing unnecessary calls to pause() and load() when starting playback. These calls may cause unexpected behavior or unnecessary reloading of the audio.

Consider updating the function as follows:

const togglePlayPause = () => {
  if (isPlaying) {
    audioRef.current?.pause();
    setIsPlaying(false);
  } else {
    audioRef.current?.play();
    setIsPlaying(true);
  }
};

This simplification will make the function more straightforward and potentially improve the user experience by avoiding unnecessary audio reloading.

Comment on lines +195 to +516
{elem}
</Typography>
</Box>
)}
</React.Fragment>
))}
</>
)}
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
marginTop: "20px",
marginBottom: "30px",
}}
>
{words?.map((elem) => (
<Box
key={elem}
className={`${
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? `audioSelectedWord`
: `audioSelectedWrongWord ${shake ? "shakeImage" : ""}`
: ""
}`}
onClick={() => handleWord(elem)}
sx={{
textAlign: "center",
px: "25px",
py: "12px",
// background: "transparent",
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
}}
>
<span
style={{
color:
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "32px",
fontFamily: "Quicksand",
}}
>
{elem}
</span>
</Box>
))}
</Box>
{
<Box sx={{ display: "flex", justifyContent: "center" }}>
<VoiceAnalyser
setVoiceText={setVoiceText}
setRecordedAudio={setRecordedAudio}
setVoiceAnimate={setVoiceAnimate}
storyLine={storyLine}
dontShowListen={true}
// updateStory={updateStory}
originalText={parentWords}
enableNext={getEnableButton()}
handleNext={handleNext}
{...{
contentId,
contentType,
currentLine: currentStep - 1,
playTeacherAudio,
callUpdateLearner,
isShowCase,
setEnableNext,
showOnlyListen: selectedWord != wordToCheck,
setOpenMessageDialog,
}}
/>
</Box>
}
{/* <Box
sx={{
display: "flex",
justifyContent: "center",
mt: "22px",
cursor: selectedWord ? "pointer" : "not-allowed",
}}
onClick={handleNext}
>
<SubmitButton opacity={selectedWord ? 1 : 0.3} />
</Box> */}
</MainLayout>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting sub-components for better organization

The render method of this component is quite large and complex. Consider extracting some of the logic into separate sub-components. For example, you could create separate components for the audio player and the fill-in-the-blank exercise. This would improve readability and maintainability of the code.

Example:

const AudioPlayer = ({ contentId, lang, isPlaying, togglePlayPause, progressBarWidth }) => {
  // Audio player JSX
};

const FillInTheBlankExercise = ({ sentences, selectedWord, handleWord }) => {
  // Fill in the blank JSX
};

// In the main render method
return (
  <MainLayout {...props}>
    {type === "audio" ? (
      <AudioPlayer {...audioProps} />
    ) : (
      <FillInTheBlankExercise {...fillInTheBlankProps} />
    )}
    {/* ... rest of the JSX */}
  </MainLayout>
);

This refactoring would make the main component more manageable and easier to understand.

Improve accessibility with ARIA attributes

The component could benefit from improved accessibility. Consider adding appropriate ARIA attributes to interactive elements. For example:

<Box
  role="button"
  aria-label={isPlaying ? "Pause audio" : "Play audio"}
  onClick={togglePlayPause}
>
  {isPlaying ? <StopAudioButton /> : <PlayAudioButton />}
</Box>

Adding these attributes will make the component more accessible to users relying on screen readers or other assistive technologies.

🧰 Tools
🪛 Biome

[error] 300-300: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

Comment on lines +183 to +185
const progressBarWidth = isNaN(currrentProgress / duration)
? 0
: currrentProgress / duration;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use Number.isNaN for safer type checking

The use of isNaN can lead to unexpected results due to type coercion. It's safer to use Number.isNaN for precise NaN checking.

Replace:

const progressBarWidth = isNaN(currrentProgress / duration)
  ? 0
  : currrentProgress / duration;

With:

const progressBarWidth = Number.isNaN(currrentProgress / duration)
  ? 0
  : currrentProgress / duration;

This change will ensure more predictable behavior when dealing with potential NaN values.

🧰 Tools
🪛 Biome

[error] 183-183: 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)

Comment on lines +174 to +178
useEffect(() => {
if (!enableNext) {
setAnswer({ text: "", audio_url: "", image_url: "", isAns: false });
}
}, [parentWords]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix useEffect dependencies to ensure proper reset of answer state.

The current useEffect hook may not behave as intended due to missing dependencies.

Update the useEffect hook to include all necessary dependencies:

 useEffect(() => {
   if (!enableNext) {
     setAnswer({ text: "", audio_url: "", image_url: "", isAns: false });
   }
-}, [parentWords]);
+}, [enableNext, parentWords]);

This ensures that the effect runs when enableNext changes, properly resetting the answer state when moving to a new question.

📝 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
useEffect(() => {
if (!enableNext) {
setAnswer({ text: "", audio_url: "", image_url: "", isAns: false });
}
}, [parentWords]);
useEffect(() => {
if (!enableNext) {
setAnswer({ text: "", audio_url: "", image_url: "", isAns: false });
}
}, [enableNext, parentWords]);

Comment on lines +180 to +193
const handleAnswerFillInTheBlank = (word) => {
setAnswer(word);

const isSoundCorrect = word.isAns;
let audio = new Audio(isSoundCorrect ? correctSound : wrongSound);
if (!isSoundCorrect) {
setEnableNext(false);
}
audio.play();
setShake(true);
setTimeout(() => {
setShake(false);
}, 800);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enable 'Next' button when the answer is correct

The handleAnswerFillInTheBlank function correctly handles setting the answer state and playing sounds, but it doesn't enable the 'Next' button when a correct answer is selected.

Add logic to enable the next button when the answer is correct:

 const handleAnswerFillInTheBlank = (word) => {
   setAnswer(word);

   const isSoundCorrect = word.isAns;
   let audio = new Audio(isSoundCorrect ? correctSound : wrongSound);
   if (!isSoundCorrect) {
     setEnableNext(false);
+  } else {
+    setEnableNext(true);
   }
   audio.play();
   setShake(true);
   setTimeout(() => {
     setShake(false);
   }, 800);
 };

This change ensures that users can progress to the next question when they select the correct answer.

📝 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 handleAnswerFillInTheBlank = (word) => {
setAnswer(word);
const isSoundCorrect = word.isAns;
let audio = new Audio(isSoundCorrect ? correctSound : wrongSound);
if (!isSoundCorrect) {
setEnableNext(false);
}
audio.play();
setShake(true);
setTimeout(() => {
setShake(false);
}, 800);
};
const handleAnswerFillInTheBlank = (word) => {
setAnswer(word);
const isSoundCorrect = word.isAns;
let audio = new Audio(isSoundCorrect ? correctSound : wrongSound);
if (!isSoundCorrect) {
setEnableNext(false);
} else {
setEnableNext(true);
}
audio.play();
setShake(true);
setTimeout(() => {
setShake(false);
}, 800);
};

Comment on lines +346 to +379
>
{/* <img
src={image}
placeholder="image"
style={{ width: "100%", height: "auto", maxWidth: "200px" }}
/> */}
</Box>
{sentences?.map((elem, index) => (
<React.Fragment key={index}>
{elem === "dash" ? (
<Box
sx={{
display: "flex",
justifyContent: "center",
marginLeft: index > 0 && "10px",
minWidth: "120px",
height: "80px",
borderBottom: "3px solid #5F6C92",
position: "relative",
}}
>
{selectedWord && (
<Box
onClick={() => handleWord(selectedWord, true)}
className={
elem === "dash"
? selectedWord === wordToCheck
? `audioSelectedWord`
: `audioSelectedWrongWord ${
shake ? "shakeImage" : ""
}`
: ""
}
sx={{
textAlign: "center",
px: "25px",
py: "12px",
// background: "transparent",
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid ${
elem === "dash"
? selectedWord === wordToCheck
? "#58CC02"
: "#C30303"
: "#333F61"
}`,
background: "#FFF",
cursor: "pointer",
}}
>
<span
style={{
color:
elem === "dash"
? selectedWord === wordToCheck
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "32px",
fontFamily: "Quicksand",
}}
>
{selectedWord}
</span>
</Box>
)}
</Box>
) : (
<Box
sx={{
display: "flex",
justifyContent: "center",
marginLeft: index > 0 && "10px",
}}
>
<Typography
variant="h5"
component="h4"
sx={{
mb: 4,
mt: 4,
fontSize: "40px",
color: "#303050",
textAlign: "center",
fontFamily: "Quicksand",
{image && (
<img
src={image}
style={{
borderRadius: "20px",
maxWidth: "100%",
height: "200px",
}}
alt=""
/>
)}
</Grid>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Reconsider absolute positioning for better responsiveness

While the Grid layout improves the overall structure, using absolute positioning for the image might cause layout issues on different screen sizes.

Consider using relative positioning or a more flexible layout approach:

 <Grid
   item
   xs={4}
   sx={{
-    position: {
-      xs: "relative",
-      sm: "relative",
-      md: "relative",
-      lg: "absolute",
-      xl: "absolute",
-    },
-    left: {
-      xs: 0,
-      sm: 0,
-      md: "20px",
-      lg: "80px",
-    },
-    mt: {
-      lg: "300px",
-    },
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+    padding: { xs: "10px", sm: "20px", md: "30px" },
   }}
 >
   {image && (
     <img
       src={image}
       style={{
         borderRadius: "20px",
         maxWidth: "100%",
         height: "200px",
       }}
       alt=""
     />
   )}
 </Grid>

This approach uses flexbox for centering and responsive padding, which should work better across different screen sizes.

📝 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
<Grid
item
xs={4}
sx={{
position: "absolute",
left: 0,
marginTop: "40px",
marginLeft: "80px",
// Add responsiveness
width: "100%", // Full width on smaller screens
maxWidth: "500px", // Limit width on larger screens
"@media (max-width: 600px)": {
position: "relative",
marginTop: "20px", // Adjust margin on small screens
marginLeft: "20px", // Adjust margin on small screens
width: "70%", // Adjust width for smaller devices
position: {
xs: "relative", // For extra small screens
sm: "relative", // For small screens
md: "relative", // For medium screens
lg: "absolute", // Change as needed for large screens
xl: "absolute", // Change as needed for extra-large screens
},
left: {
xs: 0, // For extra small screens
sm: 0, // For small screens
md: "20px", // Adjust position for medium screens
lg: "80px",
},
"@media (min-width: 600px)": {
marginTop: "30px",
marginLeft: "50px",
mt: {
lg: "300px",
},
}}
>
{/* <img
src={image}
placeholder="image"
style={{ width: "100%", height: "auto", maxWidth: "200px" }}
/> */}
</Box>
{sentences?.map((elem, index) => (
<React.Fragment key={index}>
{elem === "dash" ? (
<Box
sx={{
display: "flex",
justifyContent: "center",
marginLeft: index > 0 && "10px",
minWidth: "120px",
height: "80px",
borderBottom: "3px solid #5F6C92",
position: "relative",
}}
>
{selectedWord && (
<Box
onClick={() => handleWord(selectedWord, true)}
className={
elem === "dash"
? selectedWord === wordToCheck
? `audioSelectedWord`
: `audioSelectedWrongWord ${
shake ? "shakeImage" : ""
}`
: ""
}
sx={{
textAlign: "center",
px: "25px",
py: "12px",
// background: "transparent",
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid ${
elem === "dash"
? selectedWord === wordToCheck
? "#58CC02"
: "#C30303"
: "#333F61"
}`,
background: "#FFF",
cursor: "pointer",
}}
>
<span
style={{
color:
elem === "dash"
? selectedWord === wordToCheck
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "32px",
fontFamily: "Quicksand",
}}
>
{selectedWord}
</span>
</Box>
)}
</Box>
) : (
<Box
sx={{
display: "flex",
justifyContent: "center",
marginLeft: index > 0 && "10px",
}}
>
<Typography
variant="h5"
component="h4"
sx={{
mb: 4,
mt: 4,
fontSize: "40px",
color: "#303050",
textAlign: "center",
fontFamily: "Quicksand",
{image && (
<img
src={image}
style={{
borderRadius: "20px",
maxWidth: "100%",
height: "200px",
}}
alt=""
/>
)}
</Grid>
<Grid
item
xs={4}
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: { xs: "10px", sm: "20px", md: "30px" },
}}
>
{image && (
<img
src={image}
style={{
borderRadius: "20px",
maxWidth: "100%",
height: "200px",
}}
alt=""
/>
)}
</Grid>

Comment on lines +485 to +536
{type === "fillInTheBlank" &&
Array.isArray(options) &&
options.map(
(elem, ind) =>
answer?.text !== elem.text && (
<Box
key={ind}
className={`${
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? `audioSelectedWord`
: `audioSelectedWrongWord ${
shake ? "shakeImage" : ""
}`
: ""
}`}
onClick={() => handleAnswerFillInTheBlank(elem)}
sx={{
textAlign: "center",
px: { xs: "10px", sm: "20px", md: "25px" }, // Responsive padding
py: { xs: "8px", sm: "10px", md: "12px" }, // Responsive padding
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
display: "flex", // Flex display for better alignment
justifyContent: "center", // Centering text
alignItems: "center", // Centering text vertically
}}
>
<span
style={{
color:
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "30px", // Responsive font size
fontFamily: "Quicksand",
}}
>
{elem?.text}
</span>
</Box>
)
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Refactor className logic and filter options before mapping

The className logic in the options rendering uses audio-related classes even for the fill-in-the-blank type. Additionally, the mapping of options might attempt to render falsy values.

Refactor the className logic and filter options before mapping:

 {type === "fillInTheBlank" &&
   Array.isArray(options) &&
-  options.map(
-    (elem, ind) =>
-      answer?.text !== elem.text && (
+  options
+    .filter(elem => elem && answer?.text !== elem.text)
+    .map((elem, ind) => (
         <Box
           key={ind}
-          className={`${
-            type === "audio" && selectedWord === elem
-              ? selectedWord === parentWords
-                ? `audioSelectedWord`
-                : `audioSelectedWrongWord ${
-                    shake ? "shakeImage" : ""
-                  }`
-              : ""
-          }`}
+          className={shake ? "shakeImage" : ""}
           onClick={() => handleAnswerFillInTheBlank(elem)}
           sx={{
             // ... (keep existing styles)
           }}
         >
           <span
             style={{
-              color:
-                type === "audio" && selectedWord === elem
-                  ? selectedWord === parentWords
-                    ? "#58CC02"
-                    : "#C30303"
-                  : "#333F61",
+              color: "#333F61",
               // ... (keep other existing styles)
             }}
           >
             {elem?.text}
           </span>
         </Box>
-      )
+    ))
 )}

This refactoring removes the audio-specific class logic for fill-in-the-blank options and filters out any potential falsy values before mapping.

📝 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
{type === "fillInTheBlank" &&
Array.isArray(options) &&
options.map(
(elem, ind) =>
answer?.text !== elem.text && (
<Box
key={ind}
className={`${
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? `audioSelectedWord`
: `audioSelectedWrongWord ${
shake ? "shakeImage" : ""
}`
: ""
}`}
onClick={() => handleAnswerFillInTheBlank(elem)}
sx={{
textAlign: "center",
px: { xs: "10px", sm: "20px", md: "25px" }, // Responsive padding
py: { xs: "8px", sm: "10px", md: "12px" }, // Responsive padding
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
display: "flex", // Flex display for better alignment
justifyContent: "center", // Centering text
alignItems: "center", // Centering text vertically
}}
>
<span
style={{
color:
type === "audio" && selectedWord === elem
? selectedWord === parentWords
? "#58CC02"
: "#C30303"
: "#333F61",
fontWeight: 600,
fontSize: "30px", // Responsive font size
fontFamily: "Quicksand",
}}
>
{elem?.text}
</span>
</Box>
)
)}
{type === "fillInTheBlank" &&
Array.isArray(options) &&
options
.filter(elem => elem && answer?.text !== elem.text)
.map((elem, ind) => (
<Box
key={ind}
className={shake ? "shakeImage" : ""}
onClick={() => handleAnswerFillInTheBlank(elem)}
sx={{
textAlign: "center",
px: { xs: "10px", sm: "20px", md: "25px" }, // Responsive padding
py: { xs: "8px", sm: "10px", md: "12px" }, // Responsive padding
m: 1,
textTransform: "none",
borderRadius: "12px",
border: `1px solid rgba(51, 63, 97, 0.10)`,
background: "#FFF",
cursor: "pointer",
opacity: disabledWords ? 0.25 : 1,
pointerEvents: disabledWords ? "none" : "initial",
display: "flex", // Flex display for better alignment
justifyContent: "center", // Centering text
alignItems: "center", // Centering text vertically
}}
>
<span
style={{
color: "#333F61",
fontWeight: 600,
fontSize: "30px", // Responsive font size
fontFamily: "Quicksand",
}}
>
{elem?.text}
</span>
</Box>
))}

Copy link

sonarcloud bot commented Oct 17, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
41.4% Duplication on New Code (required ≤ 3%)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

Catch issues before they fail your Quality Gate with our IDE extension SonarLint

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
src/views/Practice/Practice.jsx (1)

Line range hint 779-784: Simplify nested ternary operator for header property

The nested ternary operator used in setting the header property can reduce code readability and maintainability. Consider refactoring to a clearer conditional structure.

You can refactor the code as follows:

let header;
if (mechanism.name === "fillInTheBlank") {
  header = "Fill in the blank";
} else if (questions[currentQuestion]?.contentType === "image") {
  header = `Guess the below image`;
} else {
  header = `Speak the below ${questions[currentQuestion]?.contentType}`;
}

Then pass header in the component props:

{
  ...,
  header,
  ...
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 00d2c65 and 1e20c17.

📒 Files selected for processing (2)
  • src/utils/constants.js (2 hunks)
  • src/views/Practice/Practice.jsx (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/constants.js
🧰 Additional context used

Comment on lines +991 to +995
mechanism.name === "fillInTheBlank"
? "Fill in the blank"
: questions[currentQuestion]?.contentType === "image"
? `Guess the below image`
: `Speak the below ${questions[currentQuestion]?.contentType}`,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify nested ternary operator for header property

The nested ternary operator used in setting the header property can reduce code readability and maintainability. Consider refactoring to a clearer conditional structure.

You can refactor the code as follows:

let header;
if (mechanism.name === "fillInTheBlank") {
  header = "Fill in the blank";
} else if (questions[currentQuestion]?.contentType === "image") {
  header = `Guess the below image`;
} else {
  header = `Speak the below ${questions[currentQuestion]?.contentType}`;
}

Then pass header in the component props:

{
  ...,
  header,
  ...
}

Comment on lines +1006 to +1014
image: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
questions[currentQuestion]?.mechanics_data[0]?.image_url
: null,
audio: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
questions[currentQuestion]?.mechanics_data[0]?.audio_url
: null,
enableNext,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure safe access to mechanics_data[0] properties

When accessing mechanics_data[0], ensure that the array has at least one element to prevent potential runtime errors. This applies to the image and audio properties where you access mechanics_data[0]?.image_url and mechanics_data[0]?.audio_url.

Consider assigning mechanics_data[0] to a variable after checking its existence:

- image: questions[currentQuestion]?.mechanics_data
-   ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
-     questions[currentQuestion]?.mechanics_data[0]?.image_url
-   : null,
- audio: questions[currentQuestion]?.mechanics_data
-   ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
-     questions[currentQuestion]?.mechanics_data[0]?.audio_url
-   : null,
+ const mechanicsDataItem = questions[currentQuestion]?.mechanics_data?.[0];
+ image: mechanicsDataItem
+   ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
+     mechanicsDataItem.image_url
+   : null,
+ audio: mechanicsDataItem
+   ? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
+     mechanicsDataItem.audio_url
+   : null,

This ensures safe access to the properties and avoids potential undefined errors.

📝 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
image: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
questions[currentQuestion]?.mechanics_data[0]?.image_url
: null,
audio: questions[currentQuestion]?.mechanics_data
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
questions[currentQuestion]?.mechanics_data[0]?.audio_url
: null,
enableNext,
const mechanicsDataItem = questions[currentQuestion]?.mechanics_data?.[0];
image: mechanicsDataItem
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_images/` +
mechanicsDataItem.image_url
: null,
audio: mechanicsDataItem
? `${process.env.REACT_APP_AWS_S3_BUCKET_CONTENT_URL}/mechanics_audios/` +
mechanicsDataItem.audio_url
: null,
enableNext,

bharathrams pushed a commit to Bhashabyasa/nd-all-learner-ai-app that referenced this pull request Nov 25, 2024
…anks-all-dev-tn

Issueid #228824 Api integration of Fill in the blanks Mechanics
bharathrams pushed a commit to Bhashabyasa/nd-all-learner-ai-app that referenced this pull request Dec 2, 2024
…anks-all-dev-tn

Issueid #228824 Api integration of Fill in the blanks Mechanics
@coderabbitai coderabbitai bot mentioned this pull request Dec 4, 2024
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.

2 participants