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

Refactor and enhance question card option with confetti animation #2651

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

ryan-gang
Copy link
Contributor

@ryan-gang ryan-gang commented Feb 12, 2025

Convert the question card option component to TypeScript, add a confetti animation for correct answers, and improve the component's functionality by persisting the confetti state and calculating the confetti origin based on mouse position or element center.

Checklist:

  • I've thoroughly self-reviewed my changes
  • I've added tests for my changes, unless they affect admin-only areas (or are otherwise not worth testing)
  • I've verified any visual changes using Percy (add a commit with [percy] in the message to trigger)

Summary by CodeRabbit

  • New Features

    • Introduced a confetti animation that plays when a correct answer is selected, providing enhanced visual feedback during quizzes.
    • Enhanced the QuestionCardOption component with additional properties for improved context and interaction.
  • Refactor

    • Improved the interactive experience by updating the state management of question options, ensuring that the confetti effect is triggered appropriately and only once per correct answer.

@ryan-gang ryan-gang self-assigned this Feb 12, 2025
Copy link

linear bot commented Feb 12, 2025

Copy link
Contributor

coderabbitai bot commented Feb 12, 2025

Walkthrough

The changes update the question card options in the concept page by integrating confetti animation for correct answers. The button template now uses a helper to trigger the confetti effect upon insertion. The previous JavaScript component is removed and replaced with a new TypeScript component that defines computed properties for the option state and an action to invoke the confetti service. Additionally, the parent question card component is modified to pass confetti state and callback bindings to the option component, and it now tracks whether confetti has been shown.

Changes

File(s) Change Summary
app/components/concept-page/question-card-option.hbs,
app/components/concept-page/question-card-option.js,
app/components/concept-page/question-card-option.ts
Updated the option component: modified the button to include {{did-insert this.fireCorrectAnswerConfetti}}; removed the old JS component; introduced a new TS component with a defined Signature interface, computed properties, and an action fireCorrectAnswerConfetti that leverages the ConfettiService.
app/components/concept-page/question-card.hbs,
app/components/concept-page/question-card.ts
Enhanced the question card component: added property bindings (@questionSlug, @hasShownConfetti, @onConfettiShown) to the option component; introduced a tracked property _hasShownConfetti, a getter hasShownConfetti, and an action handleConfettiShown to manage the confetti state.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant QC as QuestionCard (Container)
    participant QCO as QuestionCardOption Component
    participant CS as ConfettiService

    U->>QC: Render question card
    QC->>QCO: Pass @questionSlug, @hasShownConfetti, @onConfettiShown
    QCO->>QCO: did-insert triggers fireCorrectAnswerConfetti()
    alt Correct answer & confetti not shown
      QCO->>CS: Request confetti animation
      CS-->>QCO: Animation completed
      QCO->>QC: Invoke onConfettiShown callback
    else Already shown or incorrect
      QCO-->>QC: No animation triggered
    end
Loading

Suggested reviewers

  • rohitpaulk

Poem

I'm a bunny hopping through code so fine,
Confetti bursts like stars that shine.
Buttons sparkle, and logic sings,
With each update, my joy takes wings.
Celebrate with leaps and gentle cheer—
A rabbit's ode to code so dear!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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

github-actions bot commented Feb 12, 2025

Test Results

  1 files  ±0    1 suites  ±0   6m 26s ⏱️ +10s
617 tests ±0  575 ✅  - 1  41 💤 ±0  0 ❌ ±0  1 🔥 +1 
632 runs  ±0  589 ✅  - 2  41 💤 ±0  1 ❌ +1  1 🔥 +1 

For more details on these errors, see this check.

Results for commit fab1d7c. ± Comparison against base commit ab2dbbb.

This pull request removes 617 and adds 617 tests. Note that renamed tests count towards both.
Chrome 132.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a collection
Chrome 132.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a course page
Chrome 132.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a track page
Chrome 132.0 ‑ Acceptance | Meta tags: it has default meta image when visiting /catalog
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | accept-offer: accepting offer when not logged in redirects to login
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | accept-offer: can accept offer
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | view: can view affiliate link when logged in
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | view: can view affiliate link when not logged in
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | view: redirects to not found if affiliate link is invalid
Chrome 132.0 ‑ Acceptance | affiliate-course-link-page | view: redirects to not found if course slug is invalid
…
Chrome 133.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a collection
Chrome 133.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a course page
Chrome 133.0 ‑ Acceptance | Meta tags: it has custom meta image when visiting a track page
Chrome 133.0 ‑ Acceptance | Meta tags: it has default meta image when visiting /catalog
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | accept-offer: accepting offer when not logged in redirects to login
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | accept-offer: can accept offer
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | view: can view affiliate link when logged in
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | view: can view affiliate link when not logged in
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | view: redirects to not found if affiliate link is invalid
Chrome 133.0 ‑ Acceptance | affiliate-course-link-page | view: redirects to not found if course slug is invalid
…
This pull request removes 41 skipped tests and adds 41 skipped tests. Note that renamed tests count towards both.
Chrome 132.0 ‑ Acceptance | course-page | view-course-stages-test: it should track when the monthly challenge banner is clicked
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > allowMultipleSelections: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > autocompletion: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > bracketMatching: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > closeBrackets: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > collapseUnchanged: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > crosshairCursor: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > drawSelection: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > dropCursor: it does something useful with the editor
Chrome 132.0 ‑ Integration | Component | code-mirror > Options > editable: it does something useful with the editor
…
Chrome 133.0 ‑ Acceptance | course-page | view-course-stages-test: it should track when the monthly challenge banner is clicked
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > allowMultipleSelections: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > autocompletion: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > bracketMatching: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > closeBrackets: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > collapseUnchanged: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > crosshairCursor: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > drawSelection: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > dropCursor: it does something useful with the editor
Chrome 133.0 ‑ Integration | Component | code-mirror > Options > editable: it does something useful with the editor
…

♻️ This comment has been updated with latest results.

Copy link

codecov bot commented Feb 12, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
576 1 575 41
View the top 1 failed test(s) by shortest run time
Chrome 133.0 Integration | Component | code-mirror &gt; Options &gt; filename: it doesn't break the editor when passed
Stack Traces | 5.04s run time
Test took longer than 5000ms; test timed out.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link

codecov bot commented Feb 12, 2025

Bundle Report

Changes will increase total bundle size by 3.04kB (0.01%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
client-array-push 36.11MB 3.04kB (0.01%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: client-array-push

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/chunk.*.js 1.3kB 332.13kB 0.39%
assets/chunk.*.js 8 bytes 403.1kB 0.0%
assets/chunk.*.js 1.63kB 2.44MB 0.07%
assets/chunk.*.js 5 bytes 62.46kB 0.01%
assets/chunk.*.js 1 bytes 12.04kB 0.01%
assets/chunk.*.js -1 bytes 53.02kB -0.0%
assets/chunk.*.js 1 bytes 22.85kB 0.0%
assets/chunk.*.js 5 bytes 107.91kB 0.0%
assets/chunk.*.js 85 bytes 30.8kB 0.28%
assets/chunk.*.js 13 bytes 160.43kB 0.01%
assets/chunk.*.js -1 bytes 234.06kB -0.0%

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
app/services/confetti.ts (1)

30-54: Add blank lines for better code readability.

The method implementation looks good, but add blank lines before conditional statements to improve readability.

 const mouseEvent = window.event as MouseEvent;
+
 if (mouseEvent) {
   const x = mouseEvent.clientX;
   const y = mouseEvent.clientY;
+
   if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) {
     return {
🧰 Tools
🪛 ESLint

[error] 40-49: Expected blank line before this statement.

(padding-line-between-statements)


[error] 43-48: Expected blank line before this statement.

(padding-line-between-statements)

🪛 GitHub Actions: Test

[error] 40-40: Expected blank line before this statement padding-line-between-statements


[error] 43-43: Expected blank line before this statement padding-line-between-statements

app/components/concept-page/question-card-option.ts (1)

65-80: Consider adding error handling for confetti animation.

The implementation looks good, but consider handling potential failures in the confetti animation.

 @action
 async fireCorrectAnswerConfetti(element: HTMLElement) {
   if (this.hasShownConfetti || !this.isSelectedAndCorrect) {
     return;
   }

   this.hasShownConfetti = true;
   this.localStorage.setItem(this.storageKey, 'true');
-  await this.confetti.fireFromMousePositionOrElement(element, {
-    particleCount: 50,
-    spread: 60,
-    startVelocity: 20,
-    colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
-    disableForReducedMotion: true,
-  });
+  try {
+    await this.confetti.fireFromMousePositionOrElement(element, {
+      particleCount: 50,
+      spread: 60,
+      startVelocity: 20,
+      colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
+      disableForReducedMotion: true,
+    });
+  } catch (error) {
+    console.error('Failed to show confetti animation:', error);
+    // Animation failure shouldn't affect the user experience
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab2dbbb and b860079.

📒 Files selected for processing (5)
  • app/components/concept-page/question-card-option.hbs (1 hunks)
  • app/components/concept-page/question-card-option.js (0 hunks)
  • app/components/concept-page/question-card-option.ts (1 hunks)
  • app/components/concept-page/question-card.hbs (1 hunks)
  • app/services/confetti.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • app/components/concept-page/question-card-option.js
🧰 Additional context used
🪛 ESLint
app/services/confetti.ts

[error] 40-49: Expected blank line before this statement.

(padding-line-between-statements)


[error] 43-48: Expected blank line before this statement.

(padding-line-between-statements)

🪛 GitHub Actions: Test
app/services/confetti.ts

[error] 40-40: Expected blank line before this statement padding-line-between-statements


[error] 43-43: Expected blank line before this statement padding-line-between-statements

🔇 Additional comments (4)
app/services/confetti.ts (1)

30-54: Consider adding type safety for MouseEvent.

The implementation looks good, but there's a potential type safety concern with window.event.

  1. window.event is deprecated and not available in all browsers
  2. Type casting to MouseEvent might fail

Consider using the following pattern instead:

-const mouseEvent = window.event as MouseEvent;
+const mouseEvent = window.event instanceof MouseEvent ? window.event : null;

Also applies to: 75-84

🧰 Tools
🪛 ESLint

[error] 40-49: Expected blank line before this statement.

(padding-line-between-statements)


[error] 43-48: Expected blank line before this statement.

(padding-line-between-statements)

🪛 GitHub Actions: Test

[error] 40-40: Expected blank line before this statement padding-line-between-statements


[error] 43-43: Expected blank line before this statement padding-line-between-statements

app/components/concept-page/question-card-option.ts (1)

8-21: LGTM! Well-structured TypeScript interface.

The interface definition is comprehensive and properly typed.

app/components/concept-page/question-card-option.hbs (1)

8-9: LGTM! Clean integration of confetti animation.

The did-insert helper is properly integrated with the TypeScript component.

app/components/concept-page/question-card.hbs (1)

14-14: LGTM! Proper data flow for confetti state persistence.

The questionSlug prop is correctly passed to enable confetti state persistence per question.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (1)
app/services/confetti.ts (1)

77-86: Add JSDoc documentation and update method signature.

The method needs documentation and should be updated to work with the suggested changes to the calculation method.

Add documentation and update the signature:

+ /**
+  * Fires confetti from either the mouse position (if mouse is over the element)
+  * or from the element's center.
+  * @param element - The HTML element to use as reference
+  * @param mouseEvent - Optional mouse event to determine confetti origin
+  * @param options - Optional confetti configuration options
+  * @returns Promise that resolves when confetti animation starts
+  */
- async fireFromMousePositionOrElement(element: HTMLElement, options: confetti.Options = {}) {
+ async fireFromMousePositionOrElement(
+   element: HTMLElement,
+   mouseEvent?: MouseEvent,
+   options: confetti.Options = {}
+ ) {
    if (this.fastboot.isFastBoot) {
      return;
    }

-   options.origin = this.#calculateConfettiOriginBasedOnMousePositionOrElementCenter(element);
+   options.origin = this.#calculateConfettiOriginBasedOnMousePositionOrElementCenter(element, mouseEvent);

    return confetti(options);
  }

The lack of test coverage for this method needs to be addressed. Would you like me to help generate test cases?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 80-80: app/services/confetti.ts#L80
Added line #L80 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b860079 and 6be2d4b.

📒 Files selected for processing (1)
  • app/services/confetti.ts (2 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/services/confetti.ts

[warning] 42-43: app/services/confetti.ts#L42-L43
Added lines #L42 - L43 were not covered by tests


[warning] 46-46: app/services/confetti.ts#L46
Added line #L46 was not covered by tests


[warning] 80-80: app/services/confetti.ts#L80
Added line #L80 was not covered by tests

Comment on lines 30 to 55
#calculateConfettiOriginBasedOnMousePositionOrElementCenter(element: HTMLElement) {
// Get viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;

// Get the element's bounding rectangle
const rect = element.getBoundingClientRect();

// if mouseEvent is inside the rect, return the mouseEvent's position
const mouseEvent = window.event as MouseEvent;

if (mouseEvent) {
const x = mouseEvent.clientX;
const y = mouseEvent.clientY;

if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) {
return {
x: x / viewportWidth,
y: y / viewportHeight,
};
}
}

// otherwise, return the rect's center position
return this.#calculateConfettiOrigin(element);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace window.event with an explicit event parameter for better browser compatibility.

The current implementation has several concerns:

  1. Using window.event is non-standard and may not be supported in all browsers
  2. Type casting window.event as MouseEvent without null checks is unsafe

Consider refactoring to accept an explicit MouseEvent parameter:

- #calculateConfettiOriginBasedOnMousePositionOrElementCenter(element: HTMLElement) {
+ #calculateConfettiOriginBasedOnMousePositionOrElementCenter(element: HTMLElement, mouseEvent?: MouseEvent) {
    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;

    const rect = element.getBoundingClientRect();

-   const mouseEvent = window.event as MouseEvent;
-
    if (mouseEvent) {
      const x = mouseEvent.clientX;
      const y = mouseEvent.clientY;

The lack of test coverage for this method needs to be addressed. Would you like me to help generate test cases?

📝 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
#calculateConfettiOriginBasedOnMousePositionOrElementCenter(element: HTMLElement) {
// Get viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Get the element's bounding rectangle
const rect = element.getBoundingClientRect();
// if mouseEvent is inside the rect, return the mouseEvent's position
const mouseEvent = window.event as MouseEvent;
if (mouseEvent) {
const x = mouseEvent.clientX;
const y = mouseEvent.clientY;
if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) {
return {
x: x / viewportWidth,
y: y / viewportHeight,
};
}
}
// otherwise, return the rect's center position
return this.#calculateConfettiOrigin(element);
}
#calculateConfettiOriginBasedOnMousePositionOrElementCenter(element: HTMLElement, mouseEvent?: MouseEvent) {
// Get viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Get the element's bounding rectangle
const rect = element.getBoundingClientRect();
// if mouseEvent is inside the rect, return the mouseEvent's position
if (mouseEvent) {
const x = mouseEvent.clientX;
const y = mouseEvent.clientY;
if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) {
return {
x: x / viewportWidth,
y: y / viewportHeight,
};
}
}
// otherwise, return the rect's center position
return this.#calculateConfettiOrigin(element);
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 42-43: app/services/confetti.ts#L42-L43
Added lines #L42 - L43 were not covered by tests


[warning] 46-46: app/services/confetti.ts#L46
Added line #L46 was not covered by tests

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
app/components/concept-page/question-card-option.ts (2)

23-52: Consider optimizing computed properties.

The computed properties are well-organized but could be simplified to improve readability and reduce duplication.

Consider this refactoring:

-  get isSelectedAndCorrect() {
-    return this.args.isSubmitted && this.args.option.isSelected && this.args.option.is_correct;
-  }
-
-  get isSelectedAndUnsubmitted() {
-    return !this.args.isSubmitted && this.args.option.isSelected;
-  }
-
-  get isSelectedAndWrong() {
-    return this.args.isSubmitted && this.args.option.isSelected && !this.args.option.is_correct;
-  }
-
-  get isSubmitted() {
-    return this.args.isSubmitted;
-  }
-
-  get isUnselected() {
-    return !this.args.option.isSelected;
-  }
-
-  get isUnselectedAndCorrect() {
-    return this.args.isSubmitted && !this.args.option.isSelected && this.args.option.is_correct;
-  }
+  // Base states
+  get isSubmitted() {
+    return this.args.isSubmitted;
+  }
+
+  get isSelected() {
+    return this.args.option.isSelected;
+  }
+
+  // Derived states
+  get isSelectedAndCorrect() {
+    return this.isSubmitted && this.isSelected && this.isCorrect;
+  }
+
+  get isSelectedAndUnsubmitted() {
+    return !this.isSubmitted && this.isSelected;
+  }
+
+  get isSelectedAndWrong() {
+    return this.isSubmitted && this.isSelected && !this.isCorrect;
+  }
+
+  get isUnselectedAndCorrect() {
+    return this.isSubmitted && !this.isSelected && this.isCorrect;
+  }

54-70: Remove debug logging and extract confetti configuration.

The confetti action is well-implemented, but has two areas for improvement:

  1. Remove the console.log statement
  2. Extract the hardcoded confetti configuration

Consider this refactoring:

+  // Define confetti configuration as a static property or move to a config file
+  static CONFETTI_CONFIG = {
+    particleCount: 50,
+    spread: 60,
+    startVelocity: 20,
+    colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
+    disableForReducedMotion: true,
+  };
+
   @action
   async fireCorrectAnswerConfetti(element: HTMLElement) {
-    console.log('fireCorrectAnswerConfetti', this.args.hasShownConfetti, this.isSelectedAndCorrect);
     if (this.args.hasShownConfetti || !this.isSelectedAndCorrect) {
       return;
     }

     this.args.onConfettiShown();

-    await this.confetti.fireFromElement(element, {
-      particleCount: 50,
-      spread: 60,
-      startVelocity: 20,
-      colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
-      disableForReducedMotion: true,
-    });
+    await this.confetti.fireFromElement(element, QuestionCardOptionComponent.CONFETTI_CONFIG);
   }
🧰 Tools
🪛 ESLint

[error] 57-59: Expected blank line before this statement.

(padding-line-between-statements)

app/components/concept-page/question-card.ts (1)

144-149: Consider reordering methods for better organization.

The method implementation is correct, but it should be moved before handleShowExplanationClick to maintain a consistent order of class members.

Apply this diff to reorder the methods:

-  @action
-  handleShowExplanationClick() {
-    this.handleOptionSelected(this.args.question.correctOptionIndex);
-  }
-
   @action
   handleConfettiShown() {
     next(() => {
       this._hasShownConfetti = true;
     });
   }
+
+  @action
+  handleShowExplanationClick() {
+    this.handleOptionSelected(this.args.question.correctOptionIndex);
+  }
🧰 Tools
🪛 ESLint

[error] 144-149: Member handleConfettiShown should be declared before member handleShowExplanationClick.

(@typescript-eslint/member-ordering)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6be2d4b and a7241f3.

📒 Files selected for processing (3)
  • app/components/concept-page/question-card-option.ts (1 hunks)
  • app/components/concept-page/question-card.hbs (1 hunks)
  • app/components/concept-page/question-card.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/concept-page/question-card.hbs
🧰 Additional context used
🪛 ESLint
app/components/concept-page/question-card.ts

[error] 144-149: Member handleConfettiShown should be declared before member handleShowExplanationClick.

(@typescript-eslint/member-ordering)

app/components/concept-page/question-card-option.ts

[error] 57-59: Expected blank line before this statement.

(padding-line-between-statements)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (3)
app/components/concept-page/question-card-option.ts (2)

1-21: LGTM! Well-structured TypeScript interface.

The interface is well-defined with proper types for all props, and the imports are comprehensive.


73-77: LGTM! Proper Glint registry declaration.

The component is correctly registered for Glint type checking.

app/components/concept-page/question-card.ts (1)

19-23: LGTM! Well-structured state management.

The confetti state is properly managed using a tracked property with a clear public getter.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (1)
app/components/concept-page/question-card-option.ts (1)

6-21: Consider using more specific types in the interface.

The interface is well-structured, but could benefit from more specific types:

  • Consider using a discriminated union type for the option state
  • Add readonly modifiers to prevent accidental mutations
 export interface Signature {
   Element: HTMLButtonElement;
 
   Args: {
-    option: {
-      is_correct: boolean;
-      isSelected: boolean;
-      markdown: string;
-      explanation_markdown: string;
+    readonly option: {
+      readonly is_correct: boolean;
+      readonly isSelected: boolean;
+      readonly markdown: string;
+      readonly explanation_markdown: string;
     };
-    isSubmitted: boolean;
-    questionSlug: string;
-    hasShownConfetti: boolean;
-    onConfettiShown: () => void;
+    readonly isSubmitted: boolean;
+    readonly questionSlug: string;
+    readonly hasShownConfetti: boolean;
+    readonly onConfettiShown: () => void;
   };
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7241f3 and fab1d7c.

📒 Files selected for processing (2)
  • app/components/concept-page/question-card-option.ts (1 hunks)
  • app/components/concept-page/question-card.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/concept-page/question-card.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (2)
app/components/concept-page/question-card-option.ts (2)

23-53: Well-structured computed properties!

The component's state management is well-organized with clear, single-responsibility computed properties that make the component's behavior easy to understand and maintain.


74-78: LGTM!

The Glint registry entry is properly configured, enabling type checking in templates.

Comment on lines +54 to +71
@action
async fireCorrectAnswerConfetti(element: HTMLElement) {
console.log('fireCorrectAnswerConfetti', this.args.hasShownConfetti, this.isSelectedAndCorrect);

if (this.args.hasShownConfetti || !this.isSelectedAndCorrect) {
return;
}

this.args.onConfettiShown();

await this.confetti.fireFromElement(element, {
particleCount: 50,
spread: 60,
startVelocity: 20,
colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
disableForReducedMotion: true,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up confetti implementation.

A few improvements could be made to the confetti implementation:

  1. Remove the console.log statement
  2. Consider extracting magic numbers into named constants
  3. Add error handling for the confetti service call
+  private readonly CONFETTI_CONFIG = {
+    particleCount: 50,
+    spread: 60,
+    startVelocity: 20,
+    colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
+    disableForReducedMotion: true,
+  } as const;

   @action
   async fireCorrectAnswerConfetti(element: HTMLElement) {
-    console.log('fireCorrectAnswerConfetti', this.args.hasShownConfetti, this.isSelectedAndCorrect);
-
     if (this.args.hasShownConfetti || !this.isSelectedAndCorrect) {
       return;
     }

     this.args.onConfettiShown();

-    await this.confetti.fireFromElement(element, {
-      particleCount: 50,
-      spread: 60,
-      startVelocity: 20,
-      colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
-      disableForReducedMotion: true,
-    });
+    try {
+      await this.confetti.fireFromElement(element, this.CONFETTI_CONFIG);
+    } catch (error) {
+      console.error('Failed to show confetti animation:', error);
+      // Animation failure shouldn't break the app
+    }
   }
📝 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
@action
async fireCorrectAnswerConfetti(element: HTMLElement) {
console.log('fireCorrectAnswerConfetti', this.args.hasShownConfetti, this.isSelectedAndCorrect);
if (this.args.hasShownConfetti || !this.isSelectedAndCorrect) {
return;
}
this.args.onConfettiShown();
await this.confetti.fireFromElement(element, {
particleCount: 50,
spread: 60,
startVelocity: 20,
colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
disableForReducedMotion: true,
});
}
private readonly CONFETTI_CONFIG = {
particleCount: 50,
spread: 60,
startVelocity: 20,
colors: ['#22c55e', '#16a34a', '#15803d'], // green colors
disableForReducedMotion: true,
} as const;
@action
async fireCorrectAnswerConfetti(element: HTMLElement) {
if (this.args.hasShownConfetti || !this.isSelectedAndCorrect) {
return;
}
this.args.onConfettiShown();
try {
await this.confetti.fireFromElement(element, this.CONFETTI_CONFIG);
} catch (error) {
console.error('Failed to show confetti animation:', error);
// Animation failure shouldn't break the app
}
}

{{if this.isSelectedAndCorrect 'bg-green-200'}}
"
{{if this.isSelectedAndCorrect 'bg-green-200'}}"
{{did-insert this.fireCorrectAnswerConfetti}}
Copy link
Member

Choose a reason for hiding this comment

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

Let's fire confetti from the "Success" message, and not from the option itself

@@ -16,11 +16,16 @@ interface Signature {

export default class QuestionCardComponent extends Component<Signature> {
@tracked selectedOptionIndex: number | null = null;
@tracked _hasShownConfetti = false;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
@tracked _hasShownConfetti = false;
@tracked hasShownConfetti = false;

We don't need a separate underscored version

Copy link
Member

@rohitpaulk rohitpaulk left a comment

Choose a reason for hiding this comment

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

Notes added

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