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

User stats page #552

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open

User stats page #552

wants to merge 1 commit into from

Conversation

erenfn
Copy link
Collaborator

@erenfn erenfn commented Feb 23, 2025

Solves #508
Ekran görüntüsü 2025-02-23 225223

@erenfn erenfn changed the base branch from master to develop February 23, 2025 22:55
Copy link
Contributor

coderabbitai bot commented Feb 23, 2025

Walkthrough

This pull request updates the tour configuration and related functionalities across both backend and frontend. In the backend, unused properties in the tour object are removed from the settings, and a new property size is added. Several migration files, controller methods, service functions, and models are updated or newly introduced to manage tours and tour popups with transaction management and enhanced validations. In parallel, the frontend undergoes significant refactors with new routes, revised forms using Formik, additional validations and UI components, and the introduction of a user statistics page.

Changes

File(s) Change Summary
backend/config/settings.js Removed triggeringFrequency, pageTargeting, and themes from tour object; added new property size with options ['small', 'medium', 'large'].
backend/migrations/0001-1.0-helperlink.js, 0008-1.0-popup.js, 0011-1.1-helperlink.js, 0012-1.1-tour.js, 0013-1.1-tour-popup.js Updated migration files: added new migrations for helper links and tours, renamed table from popup to popups, and adjusted field definitions and associations.
backend/src/controllers/guide.controller.js, backend/src/controllers/tour.controller.js Updated controller methods – added tourService calls in GuideController; refactored TourController (removed old methods; added createTour, getTourById, getToursByUserId, getTourByUrl, and updateTour).
backend/src/models/HelperLink.js, Tour.js, TourPopup.js, models/index.js In models: added new attributes (url, active, absolutePath) to HelperLink; removed obsolete fields and added new color, size, and URL fields to Tour; defined new TourPopup model and set up model associations.
backend/src/routes/tour.routes.js Restructured endpoints with new validation middleware and renamed/getToursByUserId route.
backend/src/service/guidelog.service.js, helperLink.service.js, tour.service.js Revised service layer: added new methods (getCompleteToursLogs, getTourByUrl, getIncompleteTourByUrl), refactored tour creation, update, and deletion with transaction management, and optimized link handling with bulk operations.
Test Files (under backend/src/test/ and mocks) Removed outdated tests; added new cases for validations, transaction flows within tour and helper link operations; updated mocks to reflect new properties and method changes.
Utility Files (backend/src/utils/tour.helper.js, helperLink.helper.js) Removed old validation functions and added new validators (validateUrl, tourValidator, paramsIdValidator, bodyUrlValidator).
Frontend Files (App.jsx, components, scenes, services, tests, utils) Introduced a new route for UserStatisticsPage; refactored several components (e.g., ColorTextField, LeftMenu, PopupComponent) for improved state management and validation (Formik integration), added new UI components (CustomTable) and styling, and updated service and provider defaults.
New Files (e.g., guidelogService.js, popupHelper.js, UserStatisticsPage.jsx, CSS modules) Added new services for guide logs, validation schemas for popups, a new User Statistics page component with corresponding styles, and additional helper functions in utils.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Tour API Endpoint
    participant Controller as TourController
    participant Service as TourService
    participant DB as Database

    Client->>API: POST /api/tour/add_tour
    API->>Controller: createTour(req)
    Controller->>Service: createTour(data)
    Service->>DB: Begin Transaction
    Service->>DB: Insert Tour record
    Service->>DB: Bulk create TourPopup entries
    DB-->>Service: Transaction Commit
    Service-->>Controller: Tour Created
    Controller-->>Client: Success Response
Loading
sequenceDiagram
    participant Browser
    participant Frontend as Frontend Router
    participant Statistics as UserStatisticsPage
    participant Service as guidelogService
    participant API as Backend API

    Browser->>Frontend: Navigate to /statistics
    Frontend->>Statistics: Render UserStatisticsPage
    Statistics->>Service: getAllGuideLogs()
    Service->>API: GET /guide_log/all_guide_logs
    API-->>Service: Guide Logs Data
    Service-->>Statistics: Logs Data
    Statistics-->>Browser: Render CustomTable with Logs
Loading

Possibly related PRs

Suggested reviewers

  • swoopertr
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

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

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently, please check "Code review limits" under "Moderation" settings.

Actionable comments posted: 17

🔭 Outside diff range comments (8)
frontend/src/utils/guideHelper.js (1)

4-11: 🛠️ Refactor suggestion

Mom's spaghetti moment: Let's make this error handling more robust!

The current implementation could throw if errors array is undefined.

Here's a more defensive implementation:

 export const emitToastError = (error) => {
-    if (error.response?.data) {
+    if (error.response?.data?.errors?.length) {
       for (let i = 0; i < error.response.data.errors.length; i++){
         toastEmitter.emit(TOAST_EMITTER_KEY, 'An error occurred: ' + error.response.data.errors[i].msg)
       }
     } else {
         toastEmitter.emit(TOAST_EMITTER_KEY, 'An error occurred. Please check your network connection and try again.')
     }
 }
frontend/src/scenes/links/NewLinksPopup.jsx (1)

171-176: ⚠️ Potential issue

Add missing prop type validations.

The setIsEdit prop is used but not included in the PropTypes validation.

 NewLinksPopup.propTypes = {
   autoOpen: PropTypes.bool,
   isEdit: PropTypes.bool.isRequired,
   itemId: PropTypes.number,
   setItemsUpdated: PropTypes.func.isRequired,
+  setIsEdit: PropTypes.func.isRequired,
 };
frontend/src/scenes/links/LinkPageComponents/LinkAppearance.jsx (1)

178-180: ⚠️ Potential issue

Remove unused PropTypes declaration.

The handleSaveHelper prop is declared in PropTypes but not used in the component.

-LinkAppearance.propTypes = {
-  handleSaveHelper: PropTypes.func.isRequired,
-};
frontend/src/scenes/popup/CreatePopupPage.jsx (1)

81-84: ⚠️ Potential issue

Improve error handling in fetchPopupData.

The error handling should be more specific and avoid console.log in production code.

     } catch (error) {
-      console.log({ error });
-      emitToastError(error);
+      const errorMessage = error.response?.data?.message || 'Failed to fetch popup data';
+      emitToastError(new Error(errorMessage));
     }
frontend/src/components/Links/Settings/Settings.jsx (2)

21-22: 🛠️ Refactor suggestion

Yo dawg, we need to clean up this state management!

The component is maintaining duplicate state with both Formik (values) and local state (state). This can lead to synchronization issues and bugs.

Consider using only Formik's state management:

-const [state, setState] = useState(linkToEdit ?? defaultState);
+const formikRef = useRef();

35-52: ⚠️ Potential issue

Mom's spaghetti alert: Memory leak in useEffect!

The cleanup function doesn't handle all subscribed events and state updates.

Add cleanup for state updates:

 useEffect(() => {
   document.querySelector('#title').focus();
   if (linkToEdit) {
     const newState = {
       ...linkToEdit,
       helperId: helper.id,
     };
     setState(newState);
   } else {
     setState({
       ...defaultState,
       id: `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
     });
   }
   window.addEventListener('mousedown', handleMouseDown);

-  return () => window.removeEventListener('mousedown', handleMouseDown);
+  return () => {
+    window.removeEventListener('mousedown', handleMouseDown);
+    setState(defaultState); // Reset state on unmount
+  };
 }, []);
backend/src/test/unit/services/helperLink.test.js (1)

94-107: 🛠️ Refactor suggestion

Transaction rollback test's got me nervous!

The error test case doesn't verify the complete rollback scenario.

Add more comprehensive transaction tests:

it('should rollback all changes if link creation fails', async () => {
  HelperLinkMock.create = sinon.stub(HelperLink, 'create').resolves();
  LinkMock.bulkCreate = sinon.stub(Link, 'bulkCreate').rejects();
  
  try {
    await helperLinkService.createHelper(
      mocks.HelperLinkBuilder.helperLink(1).build(),
      mocks.LinksList
    );
  } catch (e) {
    expect(e.message).to.equal('Error creating helper');
  }
  
  expect(commit.called).to.be.false;
  expect(rollback.called).to.be.true;
  expect(HelperLinkMock.create.called).to.be.true;
});
frontend/src/tests/scenes/links/NewLinksPopup.test.jsx (1)

131-166: 🛠️ Refactor suggestion

This skipped test's got me losing myself!

The drag-and-drop test is skipped. Either fix and enable it or remove it if it's no longer relevant.

Consider using a more reliable approach for testing drag-and-drop:

import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

// Wrap your component with DndProvider in the test
const renderWithDnd = () => {
  return render(
    <DndProvider backend={HTML5Backend}>
      <NewLinksPopup />
    </DndProvider>
  );
};
🧹 Nitpick comments (42)
frontend/src/utils/guideHelper.js (1)

3-11: One shot, one opportunity: Let's modernize this code!

The function could benefit from modern JavaScript features.

Here's a more concise implementation using array methods:

 export const emitToastError = (error) => {
-    if (error.response?.data) {
-      for (let i = 0; i < error.response.data.errors.length; i++){
-        toastEmitter.emit(TOAST_EMITTER_KEY, 'An error occurred: ' + error.response.data.errors[i].msg)
-      }
-    } else {
-        toastEmitter.emit(TOAST_EMITTER_KEY, 'An error occurred. Please check your network connection and try again.')
-    }
+    const errors = error.response?.data?.errors || [];
+    if (errors.length) {
+      errors.forEach(({ msg }) => 
+        toastEmitter.emit(TOAST_EMITTER_KEY, `An error occurred: ${msg}`));
+    } else {
+      toastEmitter.emit(TOAST_EMITTER_KEY, 
+        'An error occurred. Please check your network connection and try again.');
+    }
 }
backend/src/controllers/tour.controller.js (2)

45-54: Validate the URL parameter.
The method is searching tours by url. If the url might be invalid or not well-formed, consider adding validation or normalizing it.


67-85: User ownership caution.
We remove userId from the request body but do not verify the requesting user has permission to modify this tour. Pulled a sly “knees weak” move on the data. If needed, add an ownership check or role-based validation.

backend/src/service/tour.service.js (4)

16-22: Optimal indexing.
If the table grows large, consider indexing createdBy for quick queries. That’s a detail for a separate DB-level optimization, though.


24-41: Graceful error handling.
We log the error and throw a new one. This is fine, though structured logging might help debug further. Let’s keep the code as stable as a balanced rap.


83-108: In-place update approach is good.
We remove old popups then create the new ones. This ensures consistency, though partial updates might be lost if you only intended to update certain fields. If the plan is always to replace them, all good—just a housekeeping note so you don’t get spaghetti sauce all over.


110-124: Check relationships with foreign-key constraints.
We’re destroying associated TourPopup entries before the main Tour. This approach is fine. Just ensure no references remain, or that no other tables reference these popups.

frontend/src/scenes/popup/PopupPageComponents/PopupAppearance/PopupAppearance.jsx (2)

7-7: Validation schema is neat.
Ensuring apperanceSchema is accurate will avoid unexpected data. Double-check spelling: “appearance” might be spelled differently, but that’s minor.


21-37: Consider passing form errors to onSave.
Right now, if there are form errors, we rely on the validation schema to block submission. That’s fine, but you could also show specific error messages if needed as part of onSave. Not mandatory, though.

backend/src/test/mocks/tour.mock.js (2)

67-80: New properties, no sweat.
Your introduction of headerColor, textColor, and buddies is reminiscent of unstoppable bars—just ensure types align so we don’t drop our spaghetti on the floor. Consider additional constraints if necessary.


91-109: Color validations ain't messing around.
The methods to mark header colours, text colours, and button backgrounds as invalid are tight. If you want next-level dryness, you could unify these into a single color validator method and polish that flow.

frontend/src/scenes/popup/PopupPageComponents/PopupContent/PopupContent.jsx (2)

50-56: handleDropdownChange is slick.
Nice synergy with setFieldValue and setPopupContent. This approach helps keep the left and right channels in sync, so you don’t lose your mind in the rap flow.


83-144: Text fields: watch for extra sauce.
Your usage of Formik’s handleChange plus a parent-level setPopupContent ensures real-time state sync. If your arms get heavy from more fields, consider a shared helper to handle repeated logic.

backend/src/test/e2e/tour.test.mjs (2)

47-47: Database Readiness Logging
Catching the DB readiness error and logging it is good. Keeping everything visible is wise—no hidden nerves to blow your performance.

Also applies to: 50-50


82-91: Misspelled Test Name
Notice the test name says "buttonBackgroundColorundColor". It should probably say "buttonBackgroundColor" to keep your code from stumbling over its own lines.

-it('should return 400 if "buttonBackgroundColorundColor" is not a valid hex color', ...
+it('should return 400 if "buttonBackgroundColor" is not a valid hex color', ...
backend/src/models/TourPopup.js (1)

32-39: Index missing on tourId - performance gonna drop like mom's spaghetti! 🍝

Add an index to speed up those foreign key lookups.

       tourId: {
         type: DataTypes.INTEGER,
         allowNull: false,
         references: {
           model: 'tours',
           key: 'id',
         },
+        index: true
       },
backend/migrations/0011-1.1-helperlink.js (2)

1-1: Lose yourself in the code, but drop that 'use strict'! 🎤

The 'use strict' directive is redundant in modules. Let's drop it like it's hot!

-'use strict';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)


13-51: This transaction handling is the real slim shady! 🎵

Clean transaction management with proper rollback on errors. But yo, we might want to log those errors before they vanish into thin air!

-    } catch {
+    } catch (error) {
+      console.error('Migration failed:', error);
       await transaction.rollback();
+      throw error;
     }
backend/migrations/0013-1.1-tour-popup.js (2)

1-1: Drop that 'use strict' like a hot beat! 🎵

Same deal as before, the 'use strict' directive isn't needed in modules.

-'use strict';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)


13-45: This table structure is spitting straight fire! 🔥

The schema is tight with proper constraints and that sweet CASCADE action on the foreign key. But yo, consider adding some timestamps for tracking!

       tourId: {
         // ... existing config ...
       },
+      createdAt: {
+        type: Sequelize.DATE,
+        allowNull: false,
+        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
+      },
+      updatedAt: {
+        type: Sequelize.DATE,
+        allowNull: false,
+        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
+      },
frontend/src/utils/popupHelper.js (1)

3-4: Extract regex patterns into named constants

Consider extracting these regex patterns into well-named constants at the top of the file for better maintainability.

+const VALID_PATH_CHARS = '[a-zA-Z0-9_-]';
+const RELATIVE_URL_REGEX = new RegExp(`^\\/(${VALID_PATH_CHARS}+\\/?)+$`);
+const HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;

-const RELATIVE_URL_REGEX = /^\/([a-zA-Z0-9_-]+\/?)+$/;
-const COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
backend/src/utils/tour.helper.js (1)

19-36: Add missing validations for steps array

The steps array validation could be more robust:

 body('steps').isArray().withMessage('steps must be an array'),
+body('steps').custom((value) => {
+  if (!Array.isArray(value) || value.length === 0) {
+    throw new Error('At least one step is required');
+  }
+  return true;
+}),
 body('steps.*.title').isString().withMessage('Invalid value for title'),
+body('steps.*.title').notEmpty().withMessage('Title cannot be empty'),
 body('steps.*.description').isString().withMessage('Invalid value for description'),
 body('steps.*.targetElement').isString().withMessage('Invalid value for targetElement'),
-body('steps.*.order').isInt().withMessage('Invalid value for order'),
+body('steps.*.order').isInt({ min: 0 }).withMessage('Order must be a non-negative integer'),
frontend/src/components/Table/Table.jsx (1)

26-30: Enhance table accessibility

Add ARIA labels and roles to improve accessibility:

-<TableRow className={styles.tableHeader}>
+<TableRow className={styles.tableHeader} role="row">
   {tableHeaders.map((header, index) => (
-    <TableCell key={index} className={styles.heading}>{header}</TableCell>
+    <TableCell 
+      key={index} 
+      className={styles.heading}
+      role="columnheader"
+      aria-label={header}
+    >
+      {header}
+    </TableCell>
   ))}
 </TableRow>
backend/src/models/HelperLink.js (1)

64-73: Yo! The URL validation needs some error handling love!

The URL validation is missing proper error messaging. Let's make it more informative.

-      validate: {
-        customValidation(value) {
-          return validateUrl(value);
-        },
-      },
+      validate: {
+        customValidation(value) {
+          if (!validateUrl(value)) {
+            throw new Error('Invalid URL format. Please provide a valid URL.');
+          }
+        },
+      },
frontend/src/services/linksProvider.jsx (1)

5-13: Yo! These default values are looking clean!

The default values are well-structured and match the backend model. However, consider making this more maintainable.

+const COLORS = {
+  HEADER_BG: '#F8F9F8',
+  LINK_FONT: '#344054',
+  ICON: '#7F56D9',
+};

 export const DEFAULT_VALUES = {
   title: '',
-  headerBackgroundColor: '#F8F9F8',
-  linkFontColor: '#344054',
-  iconColor: '#7F56D9',
+  headerBackgroundColor: COLORS.HEADER_BG,
+  linkFontColor: COLORS.LINK_FONT,
+  iconColor: COLORS.ICON,
   url: 'https://',
   active: true,
   absolutePath: false,
 };
backend/src/models/Tour.js (1)

69-73: The URL validation needs more swagger!

The error handling could be more descriptive for better debugging.

 customValidation(value) {
   if (!validateUrl(value)) {
-    throw new Error('Invalid value for URL');
+    throw new Error(`Invalid URL format: ${value}. Please provide a valid URL starting with http:// or https://`);
   }
 },
frontend/src/services/helperLinkService.js (1)

58-59: Clean up those console logs, yo!

There are duplicate console log statements. Let's keep it clean and consistent.

Apply this diff to remove the duplicate:

-    console.log(error.message);
     console.error('Create helper link error:', error.message);
backend/src/controllers/guide.controller.js (1)

54-54: Clean up that commented code, keep it fresh!

Remove or implement the commented code. Keeping commented code makes the codebase messy and confusing.

Either remove the commented lines or implement the intended functionality:

-      //const helperLinkIds = completeHelperLogs.map(log => log.guideId);
-      //helperLinkService.getIncompleteHelpers(helperLinkIds),

Also applies to: 61-61

frontend/src/products/Popup/PopupComponent.jsx (1)

43-49: Yo, these string literals are making me nervous! 🍝

Consider extracting the button action types into constants to prevent typos and improve maintainability.

+const BUTTON_ACTIONS = {
+  CLOSE: 'close the popup',
+  OPEN_URL: 'open url',
+  OPEN_URL_NEW: 'open url in new page'
+};

 const handleButtonClick = () => {
-  if (buttonAction === 'close the popup') {
+  if (buttonAction === BUTTON_ACTIONS.CLOSE) {
     handleClose();
-  } else if (buttonAction === 'open url') {
+  } else if (buttonAction === BUTTON_ACTIONS.OPEN_URL) {
     window.open(actionButtonUrl, '_self');
-  } else if (buttonAction === 'open url in new page') {
+  } else if (buttonAction === BUTTON_ACTIONS.OPEN_URL_NEW) {
     window.open(actionButtonUrl, '_blank');
   }
 };
backend/migrations/0012-1.1-tour.js (2)

1-3: Drop that 'use strict' like it's hot! 🔥

The 'use strict' directive is redundant in ES modules as they are automatically in strict mode.

-'use strict';

 const { validateHexColor } = require('../src/utils/guide.helper');
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)


109-110: Yo, that comment's not quite right! 🤔

The comment mentions dropping 'guide_logs' table but the code drops 'tours' table.

-      // Drop the guide_logs table
+      // Drop the tours table
       await queryInterface.dropTable(TABLE_NAME, { transaction });
backend/migrations/0008-1.0-popup.js (2)

87-88: Consider adding validation before removing columns.

The migration removes columns without first checking if they exist. This could cause issues if the migration is run on a fresh database or if the columns were already removed.

-      await queryInterface.removeColumn(TABLE_NAME, 'closeButtonAction', { transaction });
+      if (await queryInterface.tableExists(TABLE_NAME)) {
+        const columns = await queryInterface.describeTable(TABLE_NAME);
+        if (columns.closeButtonAction) {
+          await queryInterface.removeColumn(TABLE_NAME, 'closeButtonAction', { transaction });
+        }
+      }

Also applies to: 101-102, 114-115


127-136: Add error handling for bulk update operation.

The bulk update operation could fail silently if any of the updates fail. Consider adding specific error handling for this operation.

       if (allPopups.length > 0) {
+        try {
           const updates = allPopups.map((val) => ({
             id: val.id,
             closeButtonAction: val.closeButtonAction,
             popupSize: val.popupSize,
             repetitionType: val.repetitionType,
           }));
           await queryInterface.bulkUpdate(TABLE_NAME, updates, null, { transaction });
+        } catch (error) {
+          console.error('Failed to update existing popups:', error);
+          throw error;
+        }
       }
frontend/src/scenes/links/NewLinksPopup.jsx (1)

56-59: Improve error handling in fetchHelperData.

The error handling could be more specific by checking for different types of errors (network, validation, etc.).

     } catch (error) {
+      if (error instanceof AxiosError) {
+        const message = error.response?.data?.errors?.[0] || 'Failed to fetch helper data';
+        emitToastError(buildToastError(message));
+      } else {
         emitToastError(buildToastError(error));
+      }
       resetHelper();
     }
frontend/src/scenes/links/LinkPageComponents/LinkAppearance.jsx (1)

22-32: Simplify handleHelperChange logic.

The current implementation has multiple conditions that could be simplified.

   const handleHelperChange = (e) => {
-    const { name, checked } = e.target;
-    let { value } = e.target;
-    if (name === 'active') {
-      value = checked;
-    }
-    if (name === 'absolutePath') {
-      value = !checked;
-    }
+    const { name, checked, value: inputValue } = e.target;
+    const value = name === 'active' ? checked : name === 'absolutePath' ? !checked : inputValue;
     setHelper((prev) => ({ ...prev, [name]: value }));
   };
frontend/src/scenes/popup/CreatePopupPage.jsx (1)

31-38: Extract default values to constants.

The default values for popup appearance and content should be extracted to constants for better maintainability.

+const DEFAULT_POPUP_APPEARANCE = {
+  headerBackgroundColor: '#F8F9F8',
+  headerColor: '#101828',
+  textColor: '#344054',
+  buttonBackgroundColor: '#7F56D9',
+  buttonTextColor: '#FFFFFF',
+  popupSize: 'Small',
+};
+
+const DEFAULT_POPUP_CONTENT = {
+  buttonRepetition: 'Show only once',
+  buttonAction: 'No action',
+  url: 'https://',
+  actionButtonUrl: 'https://',
+  actionButtonText: 'Take me to subscription page',
+};

-  const [popupAppearance, setPopupAppearance] = useState({
-    headerBackgroundColor: '#F8F9F8',
-    headerColor: '#101828',
-    textColor: '#344054',
-    buttonBackgroundColor: '#7F56D9',
-    buttonTextColor: '#FFFFFF',
-    popupSize: 'Small',
-  });
+  const [popupAppearance, setPopupAppearance] = useState(DEFAULT_POPUP_APPEARANCE);

-  const [popupContent, setPopupContent] = useState({
-    buttonRepetition: 'Show only once',
-    buttonAction: 'No action',
-    url: 'https://',
-    actionButtonUrl: 'https://',
-    actionButtonText: 'Take me to subscription page',
-  });
+  const [popupContent, setPopupContent] = useState(DEFAULT_POPUP_CONTENT);

Also applies to: 40-46

frontend/src/components/Links/Settings/Settings.jsx (1)

227-233: Hidden submit button's making me nervous!

The hidden submit button with display: none is not accessible and might cause issues with keyboard navigation.

Consider using a visually hidden class instead:

-<button type="submit" style={{ display: 'none' }}>
+<button type="submit" className={style.visuallyHidden}>
   {isSubmitting ? (
     <CircularProgress size={12} color="inherit" />
   ) : (
     'Submit'
   )}
 </button>

Add this to your CSS:

.visuallyHidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
backend/src/test/unit/controllers/tour.test.js (2)

21-29: Yo, these test descriptions ain't telling the whole story!

The test descriptions should be more descriptive and follow the given-when-then pattern.

Refactor the test description:

-it('should return 201 if all required fields are provided', async () => {
+it('should return 201 and created tour when creating a tour with valid fields', async () => {

50-58: These error messages got me sweating!

The error message structure is inconsistent across different endpoints.

Standardize error message format:

-expect(body).to.be.deep.equal({ errors: [{"msg": "Tour with the specified id does not exist"}] });
+expect(body).to.be.deep.equal({
+  error: 'Not Found',
+  errorCode: 'TOUR_NOT_FOUND',
+  message: 'Tour with the specified id does not exist'
+});
backend/src/test/unit/services/helperLink.test.js (1)

31-44: Yo, why these test assertions taking a vacation?

There are commented-out test assertions that should either be removed or uncommented.

Either remove the commented code or uncomment and fix the assertions if they're still relevant.

frontend/src/tests/scenes/links/NewLinksPopup.test.jsx (2)

199-201: These hardcoded colors making my palms sweaty!

The test uses hardcoded RGB values for color assertions, which makes the tests brittle.

Use color constants or helper functions:

const COLORS = {
  DEFAULT_HEADER_BG: 'rgb(248, 249, 248)',
  MODIFIED_HEADER_BG: 'rgb(242, 242, 242)'
};

expect(getComputedStyle(previewHeader).backgroundColor).toBe(COLORS.DEFAULT_HEADER_BG);

Also applies to: 212-214


58-77: Missing accessibility tests got me nervous!

The test suite lacks accessibility testing for form interactions.

Add accessibility tests:

it('should be keyboard navigable', async () => {
  await renderPopup();
  const addNewLinkBtn = screen.getByText('+ Add new link');
  
  // Test keyboard navigation
  await userEvent.tab();
  expect(addNewLinkBtn).toHaveFocus();
  
  // Test form submission via keyboard
  await userEvent.keyboard('{Enter}');
  expect(screen.getByTestId('settings-form')).toBeInTheDocument();
});
🛑 Comments failed to post (17)
frontend/src/scenes/statistics/UserStatisticsPage.jsx (2)

21-25: 🛠️ Refactor suggestion

Mom's spaghetti time - let's beef up that render logic!

The component's looking weak without proper loading and error states in the UI.

     return (
         <div className={styles.container}>
+            {isLoading && <div>Loading...</div>}
+            {error && <div className={styles.error}>{error}</div>}
+            {!isLoading && !error && (
             <CustomTable fullData={guideLogs} />
+            )}
         </div>
     );
📝 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.

    return (
        <div className={styles.container}>
            {isLoading && <div>Loading...</div>}
            {error && <div className={styles.error}>{error}</div>}
            {!isLoading && !error && (
                <CustomTable fullData={guideLogs} />
            )}
        </div>
    );

6-19: 🛠️ Refactor suggestion

Yo dawg, we need to handle that loading state!

The component's got mad anxiety right now - no loading state, just raw nerves. Let's add some state management to keep our users in the loop.

 const UserStatisticsPage = () => {
     const [guideLogs, setGuideLogs] = useState([]);
+    const [isLoading, setIsLoading] = useState(true);
+    const [error, setError] = useState(null);
 
     useEffect(() => {
         const fetchGuideLogs = async () => {
             try {
+                setIsLoading(true);
                 const guideLogsData = await getAllGuideLogs();
                 setGuideLogs(guideLogsData);
             } catch (err) {
                 console.error("Error fetching guide logs:", err);
+                setError("Failed to load guide logs");
+            } finally {
+                setIsLoading(false);
             }
         };
         fetchGuideLogs();
     }, []);
📝 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.

const UserStatisticsPage = () => {
    const [guideLogs, setGuideLogs] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchGuideLogs = async () => {
            try {
                setIsLoading(true);
                const guideLogsData = await getAllGuideLogs();
                setGuideLogs(guideLogsData);
            } catch (err) {
                console.error("Error fetching guide logs:", err);
                setError("Failed to load guide logs");
            } finally {
                setIsLoading(false);
            }
        };
        fetchGuideLogs();
    }, []);
backend/src/models/TourPopup.js (1)

28-31: 🛠️ Refactor suggestion

Yo, that order field needs some boundaries, fam!

The order field's running wild like loose spaghetti - let's add some validation to keep it in check.

       order: {
         type: DataTypes.INTEGER,
         allowNull: false,
+        validate: {
+          min: 0,
+          isInt: true
+        }
       },
📝 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.

      order: {
        type: DataTypes.INTEGER,
        allowNull: false,
        validate: {
          min: 0,
          isInt: true
        }
      },
frontend/src/utils/popupHelper.js (1)

6-13: ⚠️ Potential issue

Strengthen URL validation to prevent security vulnerabilities

The current URL validation is too permissive and could allow malicious URLs. Consider adding additional checks:

 const validateUrl = (url) => {
   try {
-    new URL(url);
+    const urlObj = new URL(url);
+    // Whitelist allowed protocols
+    if (!['http:', 'https:'].includes(urlObj.protocol)) {
+      return false;
+    }
+    // Optional: Whitelist allowed domains
+    const allowedDomains = ['yourdomain.com', 'api.yourdomain.com'];
+    if (!allowedDomains.includes(urlObj.hostname)) {
+      return false;
+    }
     return true;
   } catch {
     return RELATIVE_URL_REGEX.test(url);
   }
 };
📝 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.

const validateUrl = (url) => {
  try {
    const urlObj = new URL(url);
    // Whitelist allowed protocols
    if (!['http:', 'https:'].includes(urlObj.protocol)) {
      return false;
    }
    // Optional: Whitelist allowed domains
    const allowedDomains = ['yourdomain.com', 'api.yourdomain.com'];
    if (!allowedDomains.includes(urlObj.hostname)) {
      return false;
    }
    return true;
  } catch {
    return RELATIVE_URL_REGEX.test(url);
  }
};
backend/src/utils/tour.helper.js (1)

5-8: 🛠️ Refactor suggestion

Consolidate URL validation patterns

The URL regex patterns should be shared between frontend and backend to maintain consistency.

Consider creating a shared validation package or config that both frontend and backend can import.

frontend/src/components/Table/Table.jsx (1)

13-21: 🛠️ Refactor suggestion

Add PropTypes and improve type safety

The component would benefit from proper prop type definitions:

+import PropTypes from 'prop-types';

 export default function CustomTable({ fullData = [], headers = [], data = [] }) {
     // ... component code ...
 }

+CustomTable.propTypes = {
+  fullData: PropTypes.arrayOf(PropTypes.object),
+  headers: PropTypes.arrayOf(PropTypes.string),
+  data: PropTypes.arrayOf(PropTypes.array),
+};
📝 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.

import PropTypes from 'prop-types';

export default function CustomTable({ fullData = [], headers = [], data = [] }) {
    const tableHeaders = fullData.length > 0
        ? Object.keys(fullData[0])
        : headers;
    
    const tableData = fullData.length > 0
        ? fullData.map(item => Object.values(item))
        : data;
    
    // ... component logic and rendering
    
    return (
        // ... table rendering logic
    );
}

CustomTable.propTypes = {
  fullData: PropTypes.arrayOf(PropTypes.object),
  headers: PropTypes.arrayOf(PropTypes.string),
  data: PropTypes.arrayOf(PropTypes.array),
};
backend/src/models/Tour.js (1)

98-102: ⚠️ Potential issue

Yo! This association is looking sus!

The foreign key in the Tour-TourPopup association doesn't follow the conventional naming pattern.

 Tour.hasMany(models.TourPopup, {
-  foreignKey: 'tourPopupId',
+  foreignKey: 'tourId',
   as: 'steps',
   onDelete: 'CASCADE',
 });
📝 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.

    Tour.hasMany(models.TourPopup, {
      foreignKey: 'tourId',
      as: 'steps',
      onDelete: 'CASCADE',
    });
frontend/src/App.jsx (2)

49-49: ⚠️ Potential issue

Secure that statistics route, homie!

The /statistics route needs authentication protection to prevent unauthorized access to user data.

Apply this diff to add authentication:

-          <Route path="/statistics" element={<UserStatisticsPage />} />
+          <Route path="/statistics" element={<Private Component={UserStatisticsPage} />} />
📝 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.

          <Route path="/statistics" element={<Private Component={UserStatisticsPage} />} />

45-46: ⚠️ Potential issue

Yo dawg, we need to secure these routes!

Removing the Private component wrapper from /banner and /popup routes could expose sensitive admin functionality to unauthorized users. This is making me nervous, like mom's spaghetti.

Apply this diff to restore authentication:

-          <Route path="/banner" element={<BannerDefaultPage /> }/>
-          <Route path="/popup" element={<PopupDefaultPage /> }/>
+          <Route path="/banner" element={<Private Component={BannerDefaultPage} />} />
+          <Route path="/popup" element={<Private Component={PopupDefaultPage} />} />
📝 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.

          <Route path="/banner" element={<Private Component={BannerDefaultPage} />} />
          <Route path="/popup" element={<Private Component={PopupDefaultPage} />} />
backend/src/service/helperLink.service.js (1)

58-61: 🛠️ Refactor suggestion

Enhance that error handling, fam!

The error message is too generic. We need more specific error details to help with debugging.

Apply this diff to improve error handling:

-      console.log(e);
-      await t.rollback();
-      throw new Error('Error creating helper');
+      console.error('Failed to create helper:', e.message);
+      await t.rollback();
+      throw new Error(`Failed to create helper: ${e.message}`);
📝 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.

      console.error('Failed to create helper:', e.message);
      await t.rollback();
      throw new Error(`Failed to create helper: ${e.message}`);
frontend/src/services/helperLinkService.js (1)

23-39: 🛠️ Refactor suggestion

Validate those new props like your life depends on it!

The validation function isn't checking the new properties (url, active, absolutePath).

Add validation for the new properties:

 const validateHelper = (helper, links) => {
+  if (!helper?.url) {
+    throw new Error('URL is required');
+  }
+  if (typeof helper?.active !== 'boolean') {
+    throw new Error('Active status must be a boolean');
+  }
+  if (!helper?.absolutePath) {
+    throw new Error('Absolute path is required');
+  }
   // ... existing validations ...
 };
📝 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.

const validateHelper = (helper, links) => {
  if (!helper?.url) {
    throw new Error('URL is required');
  }
  if (typeof helper?.active !== 'boolean') {
    throw new Error('Active status must be a boolean');
  }
  if (!helper?.absolutePath) {
    throw new Error('Absolute path is required');
  }
  if (!helper?.title) {
    throw new Error('Header is required');
  }
  if (!helper?.headerBackgroundColor) {
    throw new Error('Header background color is required');
  }
  if (!helper?.linkFontColor) {
    throw new Error('Link font color is required');
  }
  if (!helper?.iconColor) {
    throw new Error('Icon color is required');
  }
  if (links.some((it) => !it?.title || !it?.url)) {
    throw new Error('Title and URL are required');
  }
};
backend/src/controllers/guide.controller.js (1)

67-68: ⚠️ Potential issue

Fix that error handling, it's slipping like mom's spaghetti!

The error handling in getIncompleteGuidesByUrl isn't sending a response, which will cause the request to hang.

Apply this diff to fix the error handling:

-      internalServerError('GET_INCOMPLETE_GUIDES_BY_URL_ERROR', error.message);
+      const { payload, statusCode } = internalServerError('GET_INCOMPLETE_GUIDES_BY_URL_ERROR', error.message);
+      res.status(statusCode).json(payload);
📝 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.

      const { payload, statusCode } = internalServerError('GET_INCOMPLETE_GUIDES_BY_URL_ERROR', error.message);
      res.status(statusCode).json(payload);
    }
frontend/src/products/Popup/PopupComponent.jsx (1)

46-48: ⚠️ Potential issue

Mom's spaghetti moment: Add security for external URLs! 🔒

When opening URLs, add rel="noopener noreferrer" to prevent potential security issues with window.open.

-window.open(actionButtonUrl, '_self');
+const url = new URL(actionButtonUrl, window.location.origin);
+if (url.origin === window.location.origin) {
+  window.open(url, '_self');
+} else {
+  window.open(url, '_blank', 'noopener,noreferrer');
+}
📝 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.

      const url = new URL(actionButtonUrl, window.location.origin);
      if (url.origin === window.location.origin) {
        window.open(url, '_self');
      } else {
        window.open(url, '_blank', 'noopener,noreferrer');
      }
    } else if (buttonAction === 'open url in new page') {
      window.open(actionButtonUrl, '_blank');
backend/migrations/0012-1.1-tour.js (1)

31-34: 🛠️ Refactor suggestion

Color validation needs some extra sauce! 🎨

Consider adding length validation for hex colors to prevent potential SQL injection or overflow.

 validate: {
   isHexColor(value) {
+    if (value.length > 7) {
+      throw new Error('Color value exceeds maximum length');
+    }
     validateHexColor(value, 'headerColor');
   },
 },

Also applies to: 41-44, 51-54, 61-64

frontend/src/components/Links/Settings/Settings.jsx (1)

144-147: 🛠️ Refactor suggestion

Knees weak, arms are heavy: Redundant onChange handlers!

The onChange handlers are updating both Formik and local state, which is redundant and could cause race conditions.

Use Formik's setFieldValue instead:

-onChange={(e) => {
-  handleChange(e);
-  setState((prev) => ({ ...prev, id: e.target.value }));
-}}
+onChange={handleChange}

Also applies to: 161-164, 188-191

backend/src/test/unit/controllers/tour.test.js (1)

30-42: 🛠️ Refactor suggestion

Error handling's making me lose myself in the moment!

The error test case only checks for 500 status code but doesn't verify different types of errors.

Add more error test cases:

it('should return 400 when creating a tour with invalid data', async () => {
  req.body = { ...tour().build(), name: '' };
  await tourController.createTour(req, res);
  expect(res.status.getCall(0).args[0]).to.equal(400);
});

it('should return 403 when user lacks permission', async () => {
  req.body = tour().build();
  req.user.role = 'guest';
  await tourController.createTour(req, res);
  expect(res.status.getCall(0).args[0]).to.equal(403);
});
backend/postman/Tours.postman_collection.json (1)

142-142: ⚠️ Potential issue

Yo dawg, we got a syntax error! 🚨

There's an unexpected buttonBackgroundColor text that breaks the JSON structure.

Apply this fix:

-            {buttonBackgroundColor
+            {
📝 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.

            {
🧰 Tools
🪛 Biome (1.9.4)

[error] 142-142: Property key must be double quoted

(parse)

@gorkem-bwl
Copy link
Contributor

Looks good. Are you going to use our components on the UI, instead of the generic MUI components?

@erenfn
Copy link
Collaborator Author

erenfn commented Feb 24, 2025

Do we have a custom component for tables? I used the same one we used for the settings page

@gorkem-bwl
Copy link
Contributor

Looks like we don't. Possible that we can copy one of the components from other project (VerifyWise, Checkmate or Headcount) and see how it looks? I can then suggest some improvements if needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to see PropTypes here.

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