8000 WIP - Add Free/Sales-Call/Paid badges to Modules by adamkecskes · Pull Request #7738 · codecombat/codecombat · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

WIP - Add Free/Sales-Call/Paid badges to Modules #7738

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

Merged
merged 9 commits into from
Oct 3, 2024

Conversation

adamkecskes
Copy link
Collaborator
@adamkecskes adamkecskes commented Sep 27, 2024

New component created: AccessLevelIndicator.
It has the property level<free|sales-call|paid>, and will show the badge accordingly.
Also added the User.getSubscriptionLevel for further use. The visibility of the badges could depend on its value in the future.
You can test by manually modifying the values here .

curriculum guide class view
CodeCombat_Teacher_Dashboard Cursor_and_CodeCombat_Teacher_Dashboard
indicator

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced the AccessLevelIndicator component to visually represent user access levels.
    • Added the CodeRenderer component for inline code formatting.
    • Enhanced global object me with a new method to retrieve subscription level.
    • Integrated tooltip functionality for improved user interface feedback.
    • Added new badge definitions and tooltips for subscription levels in the localization files.
    • Implemented a new state property to track fetched prepaids for teachers.
  • Bug Fixes

    • Improved the rendering logic for module names and access levels in the ModuleHeader component.
  • Documentation

    • New story files added for AccessLevelIndicator to demonstrate its usage.

These updates enhance user experience by providing clearer access level indications, improved content rendering, and better tracking of subscription statuses.

nwinter and others added 5 commits September 27, 2024 10:08
Still need to get a couple more of these in.
Teacher dashboard class view and curriculum guide, plus parent
dashboard Ozaria progress view, now read module information from
`course.modules` instead of utils.js.

We also can use i18n for various module properties.

Claude decaffeination style fixup on I18NEditCourseView.js.
Copy link
Contributor
coderabbitai bot commented Sep 27, 2024

Walkthrough

The changes in this pull request introduce a new CSS class for tooltips, enhancements to the global me object, and the addition of a Vue component for displaying user access levels. The User model is updated with a method for retrieving subscription levels, while the AccessLevelIndicator component visually represents user access. Additional modifications include updates to various components to integrate the new functionalities and improve overall structure and readability.

Changes

File Path Change Summary
.storybook/container.scss Added .tooltip class with opacity: 1 !important;. Modified .sb-show-main and .sb-story to extend %frontend-page.
.storybook/preview.js Added VTooltip library and registered it. Updated me object with getSubscriptionLevel() method returning 'free'.
app/components/common/elements/AccessLevelIndicator.vue Introduced AccessLevelIndicator component with props for level, displayText, and displayIcon.
app/components/common/elements/AccessLevelIndicator.stories.js Created new story for AccessLevelIndicator with argTypes for level, displayText, and displayIcon. Defined stories: Free, SalesCall, Paid.
app/models/User.js Added getSubscriptionLevel() method to User class returning 'free' or 'paid'.
app/locale/en.js Added new section under paywall with badge definitions and tooltips for user access levels.
ozaria/site/components/teacher-dashboard/BaseCurriculumGuide/components/ModuleHeader.vue Imported CodeRenderer and AccessLevelIndicator. Removed renderModuleName method, using code-renderer instead.
ozaria/site/components/teacher-dashboard/BaseSingleClass/index.vue Updated createModuleStatsTable method to accept an object, including access.
ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableClassFrame.vue Updated modules prop structure, replacing moduleNum with access.
ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableModuleHeader.vue Added access prop validated against CourseSchema. Integrated CodeRenderer and AccessLevelIndicator components.

Possibly related PRs

Suggested reviewers

  • mrfinch

Poem

In the garden where we play,
New tools and tricks come out to stay.
With levels of access, clear and bright,
Our code now shines, a true delight!
Hop along, let’s celebrate,
For every change, we elevate! 🐇✨


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.

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.

Base automatically changed from nwinter/refactor-module-info to master October 1, 2024 19:44
Copy link
Contributor
@mrfinch mrfinch left a comment

Choose a reason for hiding this comment

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

can you rebase so that Nick's utils.js migration changes are not in this PR. That PR is merged

@@ -170,6 +170,10 @@ module.exports = (User = (function () {
isSmokeTestUser () { return User.isSmokeTestUser(this.attributes) }
isIndividualUser () { return !this.isStudent() && !User.isTeacher(this.attributes) }

getSubscriptionLevel () {
return 'free'
Copy link
Contributor

Choose a reason for hiding this comment

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

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: 8

🧹 Outside diff range and nitpick comments (11)
app/components/common/elements/AccessLevelIndicator.stories.js (4)

1-20: LGTM! Consider adding more descriptive titles for controls.

The import statement and default export configuration look good. The argTypes are well-defined and match the PR objectives.

Consider adding more descriptive titles for the controls to improve the Storybook UI:

 argTypes: {
   level: {
     control: { type: 'select', options: ['free', 'sales-call', 'paid'] },
     description: 'The access level of the user',
+    table: { defaultValue: { summary: 'free' } },
+    name: 'Access Level',
   },
   displayText: {
     control: 'boolean',
     description: 'Whether to display the text or not',
+    table: { defaultValue: { summary: true } },
+    name: 'Display Text',
   },
   displayIcon: {
     control: 'boolean',
     description: 'Whether to display the icon or not',
+    table: { defaultValue: { summary: true } },
+    name: 'Display Icon',
   },
 },

28-33: LGTM! Consider adding more variations for the Free story.

The Free story is correctly implemented and matches the PR objectives.

Consider adding more variations to cover different combinations of displayText and displayIcon:

export const FreeTextOnly = Template.bind({})
FreeTextOnly.args = {
  level: 'free',
  displayText: true,
  displayIcon: false,
}

export const FreeIconOnly = Template.bind({})
FreeIconOnly.args = {
  level: 'free',
  displayText: false,
  displayIcon: true,
}

export const FreeBoth = Template.bind({})
FreeBoth.args = {
  level: 'free',
  displayText: true,
  displayIcon: true,
}

35-47: LGTM! Consider adding more variations for SalesCall and Paid stories.

The SalesCall and Paid stories are correctly implemented and match the PR objectives.

Similar to the suggestion for the Free story, consider adding more variations to cover different combinations of displayText and displayIcon for both SalesCall and Paid levels. This will provide a more comprehensive set of examples in the Storybook:

export const SalesCallTextOnly = Template.bind({})
SalesCallTextOnly.args = {
  level: 'sales-call',
  displayText: true,
  displayIcon: false,
}

export const SalesCallBoth = Template.bind({})
SalesCallBoth.args = {
  level: 'sales-call',
  displayText: true,
  displayIcon: true,
}

export const PaidTextOnly = Template.bind({})
PaidTextOnly.args = {
  level: 'paid',
  displayText: true,
  displayIcon: false,
}

export const PaidIconOnly = Template.bind({})
PaidIconOnly.args = {
  level: 'paid',
  displayText: false,
  displayIcon: true,
}

1-47: Overall, great implementation. Consider adding tests and improving documentation.

The AccessLevelIndicator stories are well-implemented and cover the main use cases as per the PR objectives. Great job on following Storybook best practices!

To further improve this file:

  1. Add more variations for each level to cover all combinations of displayText and displayIcon, as suggested in previous comments.
  2. Consider adding JSDoc comments to the story exports to provide more context in the Storybook UI.
  3. Think about adding unit tests for the AccessLevelIndicator component, if not already present in another file.
  4. You might want to add a brief description of the AccessLevelIndicator component at the top of the file using the parameters property in the default export:
export default {
  title: 'AccessLevelIndicator',
  component: AccessLevelIndicator,
  parameters: {
    componentSubtitle: 'A component to display the user\'s access level',
    docs: {
      description: {
        component: 'The AccessLevelIndicator displays a badge indicating whether a module is free, requires a sales call, or is paid.'
      }
    }
  },
  // ... rest of the configuration
}

These improvements will enhance the documentation and usability of the component in Storybook.

.storybook/preview.js (3)

182-184: Approve addition of getSubscriptionLevel() method with a suggestion.

The addition of the getSubscriptionLevel() method aligns with the PR objectives. However, the current implementation always returns 'free', which might lead to unexpected behavior if not updated in the future.

Consider adding a TODO comment to remind about the future implementation:

 getSubscriptionLevel() {
+  // TODO: Implement actual subscription level logic
   return 'free'
 }

235-236: Approve VTooltip registration and suggest minor cleanup.

The registration of VTooltip as a Vue plugin is correct and aligns with the PR objectives. However, the empty line after the registration can be removed for better code organization.

Consider removing the empty line after the VTooltip registration:

 Vue.use(VTooltip.default)
-

Line range hint 1-284: Summary of changes and their impact

The changes in this file successfully lay the groundwork for the AccessLevelIndicator component and subscription-based badge visibility as outlined in the PR objectives. The addition of VTooltip and the getSubscriptionLevel() method are key components for this feature.

However, there are a few areas where the code could be improved:

  1. Consistency in import syntax (using ES6 imports)
  2. Adding TODO comments for future implementations
  3. Removing unnecessary empty lines

These changes will enhance code readability and maintainability. Overall, the modifications align well with the PR's goals and provide a solid foundation for the new feature.

Consider creating a separate file for user-related functions (like getSubscriptionLevel()) in the future if more user-specific functionality is added. This will help maintain a clear separation of concerns and improve code organization.

app/components/common/elements/AccessLevelIndicator.vue (4)

60-65: Rename local variable for clarity in 'badgeText' computed property

In the badgeText computed property, the local variable is named tooltips, but it actually contains the badge text values. For better readability and maintainability, consider renaming it to badgeTexts.

Apply this diff to rename the variable:

 badgeText () {
-  const tooltips = {
+  const badgeTexts = {
     free: 'Free',
     'sales-call': 'Call Now!',
     paid: 'Premium',
   }
-  return tooltips[this.level] || ''
+  return badgeTexts[this.level] || ''
 },

67-74: Rename local variable for clarity in 'icon' computed property

Similarly, in the icon computed property, renaming the local variable from icons to iconMap or a more descriptive name can enhance code clarity.

Apply this diff to rename the variable:

 icon () {
-  const icons = {
+  const iconMap = {
     free: '✨',
     'sales-call': '📞',
     paid: '🔒',
   }
-  return icons[this.level] || ''
+  return iconMap[this.level] || ''
 },

87-97: Avoid hyphens in CSS class names

The CSS classes .badge-free, .badge-sales-call, and .badge-paid use hyphens, which can potentially lead to issues when dynamically generating class names in JavaScript. Consider replacing hyphens with underscores or using camelCase to ensure consistency.

Apply this diff to rename the classes:

-.badge-free {
+.badgeFree {
   background-color: green;
 }

-.badge-sales-call {
+.badgeSalesCall {
   background-color: orange;
 }

-.badge-paid {
+.badgePaid {
   background-color: red;
 }

And update the computed property badgeClass accordingly:

 badgeClass () {
   return {
     badge: true,
-    [`badge-${this.level}`]: true,
+    [`badge${this.level.charAt(0).toUpperCase() + this.level.slice(1)}`]: true,
   }
 },

80-85: Ensure sufficient color contrast for accessibility

The badge styles use white text on colored backgrounds. Verify that the color combinations meet the WCAG guidelines for color contrast to ensure readability for all users.

Consider adjusting the background colors or text color to meet accessibility standards. Tools like the WebAIM Contrast Checker can help assess color contrast ratios.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4f28138 and ea431cb.

📒 Files selected for processing (10)
  • .storybook/container.scss (1 hunks)
  • .storybook/preview.js (3 hunks)
  • app/components/common/elements/AccessLevelIndicator.stories.js (1 hunks)
  • app/components/common/elements/AccessLevelIndicator.vue (1 hunks)
  • app/components/common/labels/CodeRenderer.vue (1 hunks)
  • app/models/User.js (1 hunks)
  • ozaria/site/components/teacher-dashboard/BaseCurriculumGuide/components/ModuleHeader.vue (5 hunks)
  • ozaria/site/components/teacher-dashboard/BaseSingleClass/index.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableClassFrame.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableModuleHeader.vue (4 hunks)
🔇 Additional comments (22)
.storybook/container.scss (3)

3-5: Verify the impact of extending %frontend-page to Storybook components

Extending %frontend-page to .sb-show-main and .sb-story classes might affect the isolated nature of Storybook components. While this could provide consistent styling with the main application, it might also override Storybook's default styles.

Please confirm:

  1. Is this extension necessary for all Storybook components?
  2. Could this affect the ability to view components in isolation?
  3. Are there any specific styles from %frontend-page that are required for these Storybook classes?

Consider using a more specific class or mixin if only certain styles are needed, to maintain better separation between Storybook and application styles.


Line range hint 1-9: Clarify the relationship between these changes and the PR objectives

The changes in this file seem to be somewhat disconnected from the main objectives of the PR as described. The PR mentions adding Free/Sales-Call/Paid badges to Modules and introducing an AccessLevelIndicator component, but the changes in this file are related to Storybook styling and a generic tooltip.

Could you please clarify:

  1. How do these Storybook and tooltip styles relate to the AccessLevelIndicator component and the new badges?
  2. Are there other files where the main functionality described in the PR objectives is implemented?
  3. Is this file part of the necessary setup for showcasing the new component in Storybook?

Providing this context would help in understanding how these changes fit into the larger scope of the PR.


7-9: ⚠️ Potential issue

Reconsider the use of !important and clarify the purpose of the tooltip

The addition of the .tooltip class with opacity: 1 !important; raises a few concerns:

  1. The use of !important is generally discouraged as it can lead to specificity issues and make styles harder to maintain or override when needed.
  2. Setting opacity to 1 forcefully makes the tooltip always visible, which might not be the desired behavior in all cases.

Consider the following:

  1. Can we achieve the same result without using !important? If not, please explain why it's necessary here.
  2. Is the intention to make all tooltips permanently visible? If not, what specific use case is this addressing?
  3. How does this tooltip relate to the AccessLevelIndicator badges mentioned in the PR objectives?

Please provide more context on the purpose of this tooltip style and consider alternative approaches that don't rely on !important if possible.

app/components/common/labels/CodeRenderer.vue (3)

1-3: LGTM: Component structure follows Vue 2 best practices.

The component is correctly defined using the Vue 2 object-based syntax. The use of a render function instead of a template is appropriate for this use case.

Also applies to: 12-13


3-8: LGTM: Props definition is clear and follows best practices.

The 'content' prop is well-defined with a clear type (String) and is correctly set as required. This ensures that the component will always receive the necessary data to function properly.


9-11: ⚠️ Potential issue

Consider security implications and edge cases in the render function.

While the render function effectively converts backticks to <code> tags, there are a few points to consider:

  1. Security: Using innerHTML can potentially lead to XSS vulnerabilities. Consider using a sanitization library or Vue's built-in v-html directive with appropriate sanitization.

  2. Edge cases: The current regex might not handle nested backticks or escaped backticks correctly. Consider using a more robust parsing method.

  3. Error handling: Add a check for undefined or null content to prevent potential runtime errors.

Here's a suggested improvement:

render (h) {
  if (!this.content) return h('span');
  
  const sanitizedContent = this.sanitizeHTML(this.content);
  const formattedContent = this.formatCode(sanitizedContent);
  
  return h('span', { domProps: { innerHTML: formattedContent } });
},
methods: {
  sanitizeHTML(html) {
    // Implement HTML sanitization here
  },
  formatCode(content) {
    // Implement a more robust code formatting method here
    return content.replace(/`(.*?)`/g, '<code>$1</code>');
  }
}

Consider adding appropriate HTML sanitization and improving the code formatting logic.

To verify the security implications, you can run the following script:

app/components/common/elements/AccessLevelIndicator.stories.js (1)

22-26: LGTM! Template function is well-implemented.

The Template function is correctly implemented, following Storybook best practices. It dynamically binds all props and renders the AccessLevelIndicator component.

ozaria/site/components/teacher-dashboard/BaseCurriculumGuide/components/ModuleHeader.vue (5)

9-11: LGTM: New component imports added correctly.

The import statements for CodeRenderer and AccessLevelIndicator are correctly added and align with the PR objectives of introducing badges for different access levels.


18-20: LGTM: New components registered correctly.

The CodeRenderer and AccessLevelIndicator components are properly registered in the components section, allowing their use in the template.


25-25: LGTM: Improved code style with trailing commas.

The addition of trailing commas in the props section enhances code consistency and facilitates easier future modifications.

Also applies to: 29-29, 33-33, 37-37, 41-42


51-51: LGTM: Consistent code style in computed properties.

The addition of a trailing comma after getTrackCategory in the computed properties section maintains consistency with the earlier style improvements.

Also applies to: 60-60


110-111: LGTM with a question: New components integrated correctly.

The code-renderer and access-level-indicator components are properly integrated into the template, aligning with the PR objectives. The code-renderer effectively replaces the renderModuleName method.

However, could you please clarify how the getModuleInfo.access property is populated? It's used as a prop for the access-level-indicator, but its source isn't immediately clear from the provided code.

To verify the existence and population of the access property, please run the following script:

ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableClassFrame.vue (1)

120-125: Approve changes with suggestions for consistency 8000 and prop types.

The changes align with the PR objectives by introducing the 'access' property, which likely corresponds to the subscription level (free, sales-call, or paid) for each module. This is a good step towards implementing the AccessLevelIndicator functionality.

However, to ensure consistency and maintain code quality:

  1. Verify that this change doesn't break any existing functionality in the TableModuleHeader component.
  2. Update the table-module-grid component (around line 146) to use 'access' instead of 'moduleNum' for consistency.
  3. Update the prop types in the script section to reflect the new 'access' property in the modules array.

To verify the impact of these changes, please run the following script:

This script will help identify any inconsistencies in the usage of 'moduleNum' and 'access' properties across related components.

app/components/common/elements/AccessLevelIndicator.vue (1)

15-16: Verify 'ACCESS_LEVELS' import and structure

Ensure that ACCESS_LEVELS is correctly imported and structured as an array. Any changes in the schema could affect the validator function for the level prop.

Run the following script to confirm the value of ACCESS_LEVELS:

ozaria/site/components/teacher-dashboard/BaseSingleClass/table/TableModuleHeader.vue (5)

12-14: Imports for CourseSchema, CodeRenderer, and AccessLevelIndicator are appropriate


57-58: Components CodeRenderer and AccessLevelIndicator are correctly added


87-93: Ensure the 'access' prop validator correctly references the schema

The validator function uses CourseSchema.properties.modules.additionalProperties.properties.access.enum. Please verify that this path is correct and that the enum array contains the valid access levels. This will ensure that the access prop accepts only the intended values and prevents potential runtime errors.


221-221: Improved security by replacing v-html with CodeRenderer

Using the <code-renderer> component instead of v-html to display moduleHeading enhances security by reducing the risk of XSS attacks.


221-225: Ensure AccessLevelIndicator handles undefined level prop appropriately

Since the access prop has a default value of undefined, please confirm that the AccessLevelIndicator component can handle an undefined level prop without causing rendering issues or errors.

Run the following script to verify how AccessLevelIndicator handles an undefined level prop:

✅ Verification successful

AccessLevelIndicator Properly Handles Undefined level Prop

The level prop in AccessLevelIndicator.vue is defined with a default value of 'free', ensuring that it handles undefined values without causing rendering issues or errors.

  • Definition:
    level: {
      type: String,
      required: true,
      validator: value => ACCESS_LEVELS.includes(value),
      default: 'free',
    },
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that `AccessLevelIndicator` component handles undefined `level` prop safely.

# Expected result: The component should handle `level` being undefined without errors.

rg "props.*level" --multiline app/components/common/elements/AccessLevelIndicator.vue -A 10

Length of output: 93


Script:

#!/bin/bash
# Description: Extract the props definition for `level` in AccessLevelIndicator.vue.

ast-grep --lang vue --pattern $'props: {
  level: {
    $$$
  },
  $$$
}' app/components/common/elements/AccessLevelIndicator.vue

Length of output: 237


Script:

#!/bin/bash
# Description: Find all usages of the `level` prop within AccessLevelIndicator.vue.

rg "level" app/components/common/elements/AccessLevelIndicator.vue -A 10

Length of output: 1236

ozaria/site/components/teacher-dashboard/BaseSingleClass/index.vue (3)

158-164: Passing 'access' property to createModuleStatsTable

The access property is correctly passed to createModuleStatsTable, ensuring that module access levels are included as intended.


628-628: Updated method signature to include access

The method signature of createModuleStatsTable now appropriately includes the access parameter in the destructured arguments.


632-632: Including access in the returned object

The access property is properly included in the returned object from createModuleStatsTable, which will allow for correct handling of module access levels downstream.

Comment on lines +4 to +5
const VTooltip = require('v-tooltip')

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

Consider using ES6 import syntax for consistency with modern practices.

While the use of require is consistent with other imports in this file, consider using ES6 import syntax for better alignment with modern JavaScript practices. Also, the empty line after the import can be removed.

Here's a suggested change:

-const VTooltip = require('v-tooltip')
-
+import VTooltip from 'v-tooltip';
📝 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 VTooltip = require('v-tooltip')
import VTooltip from 'v-tooltip';

Comment on lines 173 to 175
8000
getSubscriptionLevel () {
return 'free'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Implement actual subscription level logic

The current implementation of getSubscriptionLevel() in app/models/User.js always returns 'free', which may not accurately reflect the user's subscription status. Given the existing methods like isPaidTeacher(), isPaidOnlineClassUser(), and premiumEndDate(), it is recommended to:

  • Utilize these existing methods to determine the actual subscription level dynamically.
  • Replace the hardcoded return value with logic that assesses the user's subscription data.
  • Ensure consistency with Vue-related subscription logic found in app/core/store/modules/me.js.
🔗 Analysis chain

Implement actual subscription level logic

The current implementation of getSubscriptionLevel() always returns 'free', which may not accurately reflect the user's subscription status. Consider the following points:

  1. Implement logic to determine the actual subscription level based on the user's data.
  2. If this is a placeholder, add a TODO comment explaining the intended functionality.
  3. There's an unaddressed past review comment suggesting a different implementation for Vue. Consider incorporating that feedback if applicable.

Could you please clarify the intended purpose of this method and its current implementation status? Is this a work in progress, or are there plans to expand its functionality in the future?

To verify the current subscription logic, we can check for existing methods or properties related to subscription status:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
echo "Searching for existing subscription-related methods:"
rg -n 'subscription|premium|isPaid' app/models/User.js

echo "\nChecking for Vue-related subscription logic:"
rg -n 'subscription' app/core/store/modules/me.js

Length of output: 958

Comment on lines 53 to 64
free: 'Explore our levels at no cost!',
'sales-call': 'Interested in more? Give us a call and unlock!',
paid: 'This content is exclusive to users with a valid license.',
}
return tooltips[this.level] || ''
},
badgeText () {
const tooltips = {
free: 'Free',
'sales-call': 'Call Now!',
paid: 'Premium',
}
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

Externalize user-facing strings for localization

The strings used in tooltipText and badgeText are hardcoded in English. For better internationalization support, consider externalizing these strings using a localization library or a translation mechanism.

Extract the strings into a localization file or use your existing localization solution to handle translations.

Comment on lines 21 to 26
level: {
type: String,
required: true,
validator: value => ACCESS_LEVELS.includes(value),
default: 'free',
},
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

Conflict between 'required' and 'default' in 'level' prop definition

The level prop is marked as both required: true and has a default value of 'free'. In Vue.js, if a prop is required, the default value will not be used because the component expects the prop to be provided. To resolve this conflict, either set required to false if you want the default value to be used when the prop is not provided, or remove the default value if the prop must always be supplied.

Apply this diff to fix the conflict:

 level: {
   type: String,
-  required: true,
+  required: false,
   validator: value => ACCESS_LEVELS.includes(value),
   default: 'free',
 },
📝 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
level: {
type: String,
required: true,
validator: value => ACCESS_LEVELS.includes(value),
default: 'free',
},
level: {
type: String,
required: false,
validator: value => ACCESS_LEVELS.includes(value),
default: 'free',
},

Comment on lines 37 to 44
isDisplayable () {
const userDisplayMap = {
free: ['free', 'sales-call'], // non-paying users will see the 'free' and 'sales-call' badges
'sales-call': ['paid'], // users after sales call will see the 'paid' badges
paid: [], // I'm not sure if we'll have this for users, but if we'll have no badges needed.
}
return userDisplayMap[me.getSubscriptionLevel()].includes(this.level)
},
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

Consider refactoring 'isDisplayable' logic

The logic in the isDisplayable computed property may be confusing and could lead to maintenance issues. Consider refactoring it for clarity.

One approach is to invert the mapping to directly map this.level to an array of user subscription levels that can view it.

 isDisplayable () {
-  const userDisplayMap = {
-    free: ['free', 'sales-call'],
-    'sales-call': ['paid'],
-    paid: [],
-  }
-  return (userDisplayMap[me.getSubscriptionLevel()] || []).includes(this.level)
+  const levelVisibilityMap = {
+    free: ['free', 'sales-call', 'paid'],
+    'sales-call': ['sales-call', 'paid'],
+    paid: ['paid'],
+  }
+  return (levelVisibilityMap[this.level] || []).includes(me.getSubscriptionLevel())
 }

This way, you define which user subscription levels can see each badge level, which might be more intuitive.

📝 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
isDisplayable () {
const userDisplayMap = {
free: ['free', 'sales-call'], // non-paying users will see the 'free' and 'sales-call' badges
'sales-call': ['paid'], // users after sales call will see the 'paid' badges
paid: [], // I'm not sure if we'll have this for users, but if we'll have no badges needed.
}
return userDisplayMap[me.getSubscriptionLevel()].includes(this.level)
},
isDisplayable () {
const levelVisibilityMap = {
free: ['free', 'sales-call', 'paid'],
'sales-call': ['sales-call', 'paid'],
paid: ['paid'],
}
return (levelVisibilityMap[this.level] || []).includes(me.getSubscriptionLevel())
},

⚠️ Potential issue

Undefined variable 'me' in 'isDisplayable' computed property

The variable me used in me.getSubscriptionLevel() is not defined within the component. This will cause a ReferenceError at runtime. Ensure that me is properly imported or accessible within the component's context.

Consider importing or injecting me into the component:

+import me from 'core/auth' // Update the import path as necessary

 export default {
   name: 'AccessLevelIndicator',

If me is a global variable, you might need to reference it as window.me or access it through a Vuex store or another state management solution.

Committable suggestion was skipped due to low confidence.

Comment on lines 39 to 44
free: ['free', 'sales-call'], // non-paying users will see the 'free' and 'sales-call' badges
'sales-call': ['paid'], // users after sales call will see the 'paid' badges
paid: [], // I'm not sure if we'll have this for users, but if we'll have no badges needed.
}
return userDisplayMap[me.getSubscriptionLevel()].includes(this.level)
},
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

Handle undefined subscription levels in 'isDisplayable'

If me.getSubscriptionLevel() returns a value not present in userDisplayMap, accessing userDisplayMap[undefined] will result in undefined, causing an error when calling includes on it. To prevent this, provide a default array when the subscription level is unrecognized.

Apply this diff to handle unrecognized subscription levels:

 return userDisplayMap[me.getSubscriptionLevel()].includes(this.level)
+      return (userDisplayMap[me.getSubscriptionLevel()] || []).includes(this.level)
📝 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
free: ['free', 'sales-call'], // non-paying users will see the 'free' and 'sales-call' badges
'sales-call': ['paid'], // users after sales call will see the 'paid' badges
paid: [], // I'm not sure if we'll have this for users, but if we'll have no badges needed.
}
return userDisplayMap[me.getSubscriptionLevel()].includes(this.level)
},
free: ['free', 'sales-call'], // non-paying users will see the 'free' and 'sales-call' badges
'sales-call': ['paid'], // users after sales call will see the 'paid' badges
paid: [], // I'm not sure if we'll have this for users, but if we'll have no badges needed.
}
return (userDisplayMap[me.getSubscriptionLevel()] || []).includes(this.level)
},

Comment on lines +69 to +73
free: '✨',
'sales-call': '📞',
paid: '🔒',
}
return icons[this.level] || ''
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

Use consistent icons instead of emojis

Using emojis for icons may lead to inconsistent rendering across different platforms and devices. It's better to use consistent icon components or SVGs that align with the application's design system.

Consider importing and using icon components from your UI library. For example:

+import FreeIcon from 'components/icons/FreeIcon.vue'
+import SalesCallIcon from 'components/icons/SalesCallIcon.vue'
+import PaidIcon from 'components/icons/PaidIcon.vue'

 // In computed property
 icon () {
-  const icons = {
-    free: '✨',
-    'sales-call': '📞',
-    paid: '🔒',
+  const icons = {
+    free: FreeIcon,
+    'sales-call': SalesCallIcon,
+    paid: PaidIcon,
   }
   return icons[this.level] || ''
 },

And update the template to use the icon components:

 <span v-if="displayIcon">
-  {{ icon }}
+  <component :is="icon" />
 </span>

Committable suggestion was skipped due to low confidence.

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: 2

🧹 Outside diff range and nitpick comments (6)
app/components/common/elements/AccessLevelIndicator.vue (3)

1-12: LGTM! Consider adding ARIA attributes for accessibility.

The template implementation looks good. The use of v-if for conditional rendering, v-tooltip for tooltips, and $t for translations are all appropriate.

Consider adding ARIA attributes to improve accessibility. For example:

 <span
   v-if="isDisplayable"
   v-tooltip.bottom="{
     content: $t(`paywall.badge_tooltip_${level}`),
   }"
   :class="badgeClass"
+  role="status"
+  :aria-label="$t(`paywall.badge_${level}`)"
 >

This change will help screen readers to properly announce the badge status.


63-82: Align badge colors with the application's design system

The current color choices for the badges (green, orange, red) might not align with your application's color scheme. Consider using color variables from your design system to ensure consistency across the application.

Here's a suggestion:

@import '~styles/variables.scss'; // Adjust the import path as needed

.badge-free {
  background-color: $color-free-badge;
}

.badge-sales-call {
  background-color: $color-sales-call-badge;
}

.badge-paid {
  background-color: $
F438
color-paid-badge;
}

This approach allows for easier maintenance and ensures consistency with your overall design.


1-82: Consider localizing access level values

While the component correctly uses $t for translating badge text and tooltips, the access level values ('free', 'sales-call', 'paid') are used as keys and might need to be localized for a fully internationalized application.

Consider creating a mapping between internal level values and localized display values. For example:

computed: {
  localizedLevel() {
    return this.$t(`paywall.access_level_${this.level}`);
  }
}

Then use localizedLevel instead of level when displaying text to the user. This allows you to keep the internal logic using English keys while presenting localized text to the user.

app/locale/en.js (3)

Line range hint 1-6291: Overall structure and organization looks good.

The file is well-organized with clear categories for different parts of the application. The use of nested objects helps keep related translations grouped together, which is a good practice for maintainability.

However, given the size of this file (over 6000 lines), it might be worth considering splitting it into smaller, more manageable files for each major section of the application. This could improve maintainability and make it easier for developers to work on specific areas without navigating such a large file.

Consider splitting this large translation file into smaller, more focused files for each major section of the application. For example:

// app/locale/en/common.js
export const common = {
  // ... common translations
};

// app/locale/en/play.js
export const play = {
  // ... play-related translations
};

// ... other sections

// app/locale/en.js
import { common } from './en/common';
import { play } from './en/play';
// ... other imports

export default {
  common,
  play,
  // ... other sections
};

This approach would make the translations more modular and easier to maintain.


Line range hint 1357-1605: The 'new_home' section is well-structured but could benefit from some improvements.

The translations in this section are comprehensive and cover various aspects of the home page. However, there are a few areas that could be enhanced:

  1. Inconsistent use of quotation marks:
    Some strings use single quotes, while others use double quotes. For consistency, it's better to stick to one style throughout.

  2. HTML tags in translation strings:
    There are several instances of HTML tags within the translation strings. While this works, it's generally better to keep markup separate from translations to improve maintainability and allow for easier styling changes.

  3. Long strings without line breaks:
    Some translation strings are quite long and could benefit from being split across multiple lines for improved readability.

Here's an example of how you could improve one of the translations:

new_home: {
  // ... other translations ...
  game_based_blurb: `
    CodeCombat is a game-based computer science program where students
    type real code and see their characters react in real time.
  `.trim(),
  // ... other translations ...
},

This approach uses template literals for multi-line strings and removes HTML tags from the translation.

Consider using a linter with rules for consistent quote usage and maximum line length to maintain consistency throughout the file.


Line range hint 2431-2859: The 'courses' section is comprehensive but could use some refinements.

This section covers a wide range of course-related translations, which is great for internationalization. However, there are a few areas that could be improved:

  1. Inconsistent use of placeholders:
    Some strings use __placeholderName__ format, while others use {{placeholderName}}. It would be better to stick to one format for consistency.

  2. Mixed use of HTML:
    Similar to the 'new_home' section, there are instances of HTML mixed with plain text. It's generally better to keep markup separate from translations.

  3. Potential for grouping:
    Some related translations (e.g., those pertaining to licenses or class management) could be grouped into sub-objects for better organization.

Here's an example of how you could improve the organization:

courses: {
  // ... other translations ...
  licenses: {
    get_enrollments_blurb: "We'll help you build a solution that meets the needs of your class, school or district.",
    see_also_our: 'See also our',
    for_more_funding_resources: 'for how to leverage CARES Act funding sources like ESSER and GEER.'
  },
  class_management: {
    add_students: 'Add Students',
    stats: 'Statistics',
    total_students: 'Total students:',
    average_time: 'Average level play time:',
    total_time: 'Total play time:',
    average_levels: 'Average levels completed:',
    total_levels: 'Total levels completed:',
    // ... other related translations ...
  },
  // ... other translations ...
},

This grouping can make it easier to find and manage related translations.

Consider using a consistent format for placeholders throughout the file, preferably __placeholderName__ as it's used more frequently in this file.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ea431cb and a77421f.

📒 Files selected for processing (3)
  • app/components/common/elements/AccessLevelIndicator.vue (1 hunks)
  • app/locale/en.js (1 hunks)
  • app/models/User.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/User.js
🔇 Additional comments (2)
app/components/common/elements/AccessLevelIndicator.vue (2)

14-35: LGTM! Props are well-defined.

The component definition and props look good. The use of CourseSchema for ACCESS_LEVELS ensures consistency with the course model.

The previous comment about the conflict between 'required' and 'default' in the 'level' prop has been addressed. The current implementation with required: false and a default value is correct.


37-44: 🛠️ Refactor suggestion

Refactor 'isDisplayable' for clarity and robustness

The current implementation of isDisplayable might be confusing and could lead to maintenance issues. Consider refactoring it for clarity and to handle potential undefined values.

Here's a suggested refactor:

isDisplayable() {
  const levelVisibilityMap = {
    free: ['free', 'sales-call', 'paid'],
    'sales-call': ['sales-call', 'paid'],
    paid: ['paid'],
  };
  const userLevel = me.getSubscriptionLevel();
  return (levelVisibilityMap[this.level] || []).includes(userLevel);
}

This approach defines which user subscription levels can see each badge level, which might be more intuitive and easier to maintain.

Also, ensure that me is properly imported or accessible within the component's context:

If me is a global variable, you might need to reference it as window.me or access it through a Vuex store or another state management solution.

Comment on lines +51 to +58
icon () {
const icons = {
free: '✨',
'sales-call': '📞',
paid: '🔒',
}
return icons[this.level] || ''
},
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

Consider using consistent icons instead of emojis

Using emojis for icons may lead to inconsistent rendering across different platforms and devices. It's better to use consistent icon components or SVGs that align with the application's design system.

Consider importing and using icon components from your UI library. For example:

import FreeIcon from 'components/icons/FreeIcon.vue'
import SalesCallIcon from 'components/icons/SalesCallIcon.vue'
import PaidIcon from 'components/icons/PaidIcon.vue'

// In computed property
icon() {
  const icons = {
    free: FreeIcon,
    'sales-call': SalesCallIcon,
    paid: PaidIcon,
  }
  return icons[this.level] || null
},

Then update the template to use the icon components:

<component :is="icon" v-if="displayIcon && icon" />

This approach ensures consistent rendering across all platforms and devices.

Comment on lines +6283 to +6291
paywall: {
badge_free: 'Free',
'badge_sales-call': 'Call Now!',
badge_paid: 'Premium',
badge_tooltip_free: 'Explore our levels at no cost!',
'badge_tooltip_sales-call': 'Interested in more? Give us a call and unlock!',
badge_tooltip_paid: 'This content is exclusive to users with a valid license.',
},
},
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

The new 'paywall' section is a good addition but needs some adjustments for consistency.

The 'paywall' section introduces translations for badges and tooltips related to content access levels. While this is a valuable addition, there are a few points to address:

  1. Inconsistent indentation:
    The 'paywall' section is not indented properly compared to other sections in the file. It should be aligned with other top-level keys.

  2. Inconsistent key naming:
    The key 'badge_sales-call' uses a hyphen, which is inconsistent with the camelCase naming convention used elsewhere in the file.

  3. Quotation marks:
    Some strings use single quotes, while others use double quotes. For consistency, it's better to stick to one style.

Here's how you could improve the 'paywall' section:

module.exports = {
  // ... other translations ...
  paywall: {
    badgeFree: 'Free',
    badgeSalesCall: 'Call Now!',
    badgePaid: 'Premium',
    badgeTooltipFree: 'Explore our levels at no cost!',
    badgeTooltipSalesCall: 'Interested in more? Give us a call and unlock!',
    badgeTooltipPaid: 'This content is exclusive to users with a valid license.'
  }
};

This refactored version:

  • Uses consistent camelCase for key names
  • Aligns with the overall structure of the file
  • Uses consistent quotation marks (single quotes in this case, but double quotes would also be fine if used consistently)

Ensure that this new section is placed logically within the overall structure of the translations object, possibly grouping it with other related sections if applicable.

@@ -170,6 +170,10 @@ module.exports = (User = (function () {
isSmokeTestUser () { return User.isSmokeTestUser(this.attributes) }
isIndividualUser () { return !this.isStudent() && !User.isTeacher(this.attributes) }

getSubscriptionLevel () {
return (this.isPaidTeacher() || this.isPremium()) ? 'paid' : 'free'
Copy link
Contributor

Choose a reason for hiding this comment

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

this is not a correct check. If u see comment in isPaidTeacher

// TODO: this doesn't actually check to see if they are paid (having prepaids), confusing
 // use isPaidTeacher from me.js in vuex store for vue

Use the check from vue store me.js or fetch prepaids and check

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

🧹 Outside diff range and nitpick comments (1)
app/core/store/modules/prepaids.js (1)

146-150: LGTM: New action to ensure prepaids are loaded

The new ensurePrepaidsLoadedForTeacher action is a good addition:

  1. It prevents unnecessary API calls by checking both the prepaids state and the new fetchedPrepaids state.
  2. The implementation is correct and efficient.

Consider adding a comment explaining the purpose of this action, as it's a key optimization for preventing redundant API calls.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a77421f and 49d9c1b.

📒 Files selected for processing (2)
  • app/components/common/elements/AccessLevelIndicator.vue (1 hunks)
  • app/core/store/modules/prepaids.js (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/common/elements/AccessLevelIndicator.vue
🔇 Additional comments (8)
app/core/store/modules/prepaids.js (8)

13-13: LGTM: State object updates

The changes to the state object look good:

  1. Adding trailing commas improves maintainability.
  2. The new fetchedPrepaids property is a good addition for tracking fetched prepaids per teacher.

Also applies to: 17-17, 21-24


28-30: LGTM: New mutation for tracking fetched prepaids

The new setFetchedPrepaidsForTeacher mutation is well-implemented:

  1. It correctly uses Vue.set for reactive updates.
  2. It corresponds to the new fetchedPrepaids state property.

36-36: LGTM: Consistent use of trailing commas

The addition of trailing commas in mutations is a positive change:

  1. It improves code consistency throughout the file.
  2. It makes future additions and modifications easier.
  3. It aligns with modern JavaScript best practices.

Also applies to: 47-47, 60-60, 92-92, 101-101


142-142: LGTM: Consistent trailing comma in getter

The addition of a trailing comma in the getJoinersForPrepaid getter is consistent with the earlier changes and doesn't affect its functionality.


154-154: LGTM: Updates to fetchPrepaidsForTeacher action

The changes to the fetchPrepaidsForTeacher action are good:

  1. Setting the fetchedPrepaids state ensures proper tracking of fetched prepaids.
  2. The added trailing comma maintains consistency with the rest of the file.

Also applies to: 166-166


182-182: LGTM: Consistent trailing comma in fetchPrepaidsForAPIClient

The addition of a trailing comma in the fetchPrepaidsForAPIClient action is consistent with earlier changes and doesn't affect its functionality.


268-268: LGTM: Consistent trailing commas in remaining actions

The addition of trailing commas in the applyLicenses, revokeLicenses, and getTestLicense actions is consistent with earlier changes:

  1. It improves overall code consistency.
  2. It makes future modifications easier.
  3. No functional changes were made to these actions.

Also applies to: 287-287, 295-297, 350-350, 358-360, 373-373, 379-380, 391-391, 404-405


Line range hint 1-405: Overall assessment: Excellent improvements

This pull request makes several positive changes to the prepaids.js module:

  1. Consistent use of trailing commas throughout the file, improving maintainability and aligning with modern JavaScript best practices.
  2. Introduction of a new fetchedPrepaids state and related mutation/action to track which teachers' prepaids have been fetched, potentially reducing unnecessary API calls.
  3. The new ensurePrepaidsLoadedForTeacher action efficiently uses the new state to prevent redundant API requests.

These changes should improve code consistency, maintainability, and potentially optimize performance. Great work!

@adamkecskes adamkecskes merged commit 06db337 into master Oct 3, 2024
2 checks passed
@adamkecskes adamkecskes deleted the adamk/mark-courses-paid-salescall-free branch October 3, 2024 07:51
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.

3 participants
0