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

Creation modal quality of life changes #467

Merged
merged 16 commits into from
Jan 26, 2025

Conversation

Blackshadow8910
Copy link
Contributor

@Blackshadow8910 Blackshadow8910 commented Jan 13, 2025

What type of PR is this?

  • feature

What this PR does / why we need it:

This PR does the following:

  • Autofills the parent location field in the create location modal
  • Focuses on the name fields when opening any create modal (Though looking at the code, I think it was already supposed to do that)
  • Rearranged the create modals to have the label and location inputs first
  • Changed the color of the multiselect border to match other components
  • Fixed the incomplete create modal shortcuts I made in Added keyboard accessible shortcut menu for create modals #457 (oops)

I think this layout for the modals is a little more consistent and efficient, since having the labels on top makes it possible to tab to the create button. Any other opinions on this would be appreciated though.

Which issue(s) this PR fixes:

Fixes #411

Summary by CodeRabbit

Release Notes

  • New Features

    • Added option to close modal by clicking outside.
    • Introduced a new multiselect component for label selection in item creation.
  • Improvements

    • Enhanced modal focus management for better user experience.
    • Refined label and location selection in create modals.
    • Updated keyboard shortcuts for creating items, locations, and labels.
  • Style Updates

    • Updated styling for multiselect component border colors.
  • User Experience

    • More intuitive modal interactions.
    • Simplified shortcut key mappings.

Copy link
Contributor

coderabbitai bot commented Jan 13, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request introduces several enhancements to frontend components, focusing on modal interactions, form management, and keyboard shortcut handling. The changes primarily involve updating component props, modifying CSS classes, and refining modal focus and interaction behaviors. Key modifications include adding a clickOutsideToClose prop to modals, updating location and label creation workflows, and revising keyboard shortcut implementations in the default layout.

Changes

File Change Summary
frontend/components/Base/Modal.vue Added clickOutsideToClose and modalTop props to control modal behavior and positioning.
frontend/components/Form/Multiselect.vue Updated border color classes for styling elements.
frontend/components/Item/CreateModal.vue Replaced whenever with watch for managing modal focus state.
frontend/components/Label/CreateModal.vue Replaced whenever with watch for managing modal state.
frontend/components/Location/CreateModal.vue Added location-related computed properties and updated modal focus logic.
frontend/components/global/QuickMenu/Modal.vue Added :click-outside-to-close="true" prop to enhance user interaction.
frontend/layouts/default.vue Updated keyboard shortcuts and added activeElement handling.
frontend/components/global/QuickMenu/Input.vue Modified watch function behavior regarding inputBoxFocused state.

Assessment against linked issues

Objective Addressed Explanation
Fill parent location field when creating location

Possibly related PRs

  • show add photo button on mobile #229: The changes in CreateModal.vue and Modal.vue are related as both involve modifications to modal behavior and structure, enhancing user interaction with modals.
  • Add char counters to inputs #234: The updates in CreateModal.vue for input validation are relevant as they also involve modal components, specifically enhancing user experience through input constraints.
  • Implement maintenance view #235: The changes in CreateModal.vue and Location/CreateModal.vue are related as they both involve modal components and enhancements to user input handling.

Suggested Reviewers

  • tankerkiller125

Security Recommendations

  1. Input Validation: Ensure that the new clickOutsideToClose prop is properly sanitized to prevent potential XSS vulnerabilities.
  2. Keyboard Shortcut Security: Validate that the new keyboard shortcut implementation doesn't introduce unintended access to sensitive actions.
  3. Modal Interaction: Implement additional checks to prevent unauthorized modal interactions, especially with the new click-outside functionality.
  4. Route Parameter Handling: Validate and sanitize locationId and labelId to prevent potential injection or manipulation.
  5. Timeout Function Security: Ensure that timeout functions used for focus management cannot be exploited for malicious purposes.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 37315f1 and ac9a579.

📒 Files selected for processing (1)
  • frontend/components/Base/Modal.vue (4 hunks)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/components/Base/Modal.vue (1)

58-62: Add rate limiting to click outside handler

The click outside handler could be triggered rapidly, potentially causing race conditions.

Consider debouncing the handler:

  if (props.clickOutsideToClose) {
+   const debouncedClose = useDebounceFn(() => {
+     close();
+   }, 300);
    onClickOutside(modalBox, () => {
-     close();
+     debouncedClose();
    });
  }
frontend/layouts/default.vue (1)

203-208: Sanitize shortcut keys before use

The current implementation directly uses shortcut keys without sanitization. While the risk is low since shortcuts are defined internally, it's good practice to sanitize all user inputs.

Apply this diff to add sanitization:

  if (option?.shortcut) {
-   const shortcutKeycode = option.shortcut.replace(/([0-9])/, "digit$&");
+   // Sanitize shortcut key to only allow specific characters
+   const sanitizedShortcut = option.shortcut.replace(/[^0-9!@#]/, "");
+   if (sanitizedShortcut !== option.shortcut) {
+     console.error('Invalid shortcut key');
+     return;
+   }
+   const shortcutKeycode = sanitizedShortcut.replace(/([0-9])/, "digit$&");
    whenever(keys[shortcutKeycode], () => {
      if (activeElement.value?.tagName !== "INPUT") {
        option.action();
      }
    });
  }
🛑 Comments failed to post (3)
frontend/components/Base/Modal.vue (1)

44-47: 💡 Codebase verification

⚠️ Potential issue

🔒 Add event listener cleanup to prevent security vulnerabilities

The click outside handler needs proper cleanup to prevent:

  • Memory leaks affecting application performance
  • Potential information disclosure if modal content remains accessible
  • Resource exhaustion from accumulated event listeners
+ const stopClickOutside = ref();

  if (props.clickOutsideToClose) {
-    onClickOutside(modalBox, () => {
+    stopClickOutside.value = onClickOutside(modalBox, () => {
      close();
    });
  }

+ onUnmounted(() => {
+   if (stopClickOutside.value) {
+     stopClickOutside.value();
+   }
+ });
🔗 Analysis chain

Ensure proper cleanup of click outside handler

The click outside handler is added conditionally but there's no cleanup when the component is unmounted.

Add cleanup to prevent memory leaks and potential security issues:

+ const stopClickOutside = ref();

  if (props.clickOutsideToClose) {
-    onClickOutside(modalBox, () => {
+    stopClickOutside.value = onClickOutside(modalBox, () => {
      close();
    });
  }

+ onUnmounted(() => {
+   if (stopClickOutside.value) {
+     stopClickOutside.value();
+   }
+ });
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find onClickOutside implementation and usage
ast-grep --pattern 'const onClickOutside = $_'

# Search for similar modal cleanup patterns
rg "onClickOutside.*modal" -A 5 -B 5

# Find unmounted cleanup patterns
ast-grep --pattern 'onUnmounted(() => {
  $$$
})'

Length of output: 4153


Script:

#!/bin/bash
# Search for onClickOutside implementation in composables
fd "use.*\.ts$" --exec cat {} \; 

# Look for vueuse imports as it might be from there
rg "@vueuse/.*onClickOutside" -A 2 -B 2

Length of output: 26563

frontend/components/Location/CreateModal.vue (1)

98-103: ⚠️ Potential issue

Validate route parameters to prevent path traversal

The location ID from route parameters needs validation to prevent potential security issues.

Add validation:

  const locationId = computed(() => {
    if (route.fullPath.includes("/location/")) {
-     return route.params.id;
+     const id = route.params.id;
+     // Ensure id is a valid UUID or your expected format
+     const validIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+     return validIdPattern.test(id) ? id : null;
    }
    return null;
  });
📝 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 locationId = computed(() => {
    if (route.fullPath.includes("/location/")) {
      const id = route.params.id;
      // Ensure id is a valid UUID or your expected format
      const validIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      return validIdPattern.test(id) ? id : null;
    }
    return null;
  });
frontend/components/Item/CreateModal.vue (1)

144-163: ⚠️ Potential issue

Validate route parameters before use

The current implementation directly uses route parameters without validation. This could potentially lead to XSS attacks if the route parameters contain malicious content.

Apply this diff to add validation:

  if (locationId.value) {
+   // Validate locationId is a valid UUID
+   if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(locationId.value)) {
+     console.error('Invalid location ID format');
+     return;
+   }
    const found = locations.value.find(l => l.id === locationId.value);
    if (found) {
      form.location = found;
    }
  }

  if (labelId.value) {
+   // Validate labelId is a valid UUID
+   if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(labelId.value)) {
+     console.error('Invalid label ID format');
+     return;
+   }
    form.labels = labels.value.filter(l => l.id === labelId.value);
  }
📝 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.

  watch(
    () => modal.value,
    (open) => {
      if (open) {
        useTimeoutFn(() => { focused.value = true }, 50);
  
        if (locationId.value) {
          // Validate locationId is a valid UUID
          if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(locationId.value)) {
            console.error('Invalid location ID format');
            return;
          }
          const found = locations.value.find(l => l.id === locationId.value);
          if (found) {
            form.location = found;
          }
        }
  
        if (labelId.value) {
          // Validate labelId is a valid UUID
          if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(labelId.value)) {
            console.error('Invalid label ID format');
            return;
          }
          form.labels = labels.value.filter(l => l.id === labelId.value);
        }
      } else {
        focused.value = false;
      }
    }

@tonyaellie
Copy link
Collaborator

tonyaellie commented Jan 13, 2025

Linting needs to be run, the labels dropdown seems to get hidden on small screens
image
The shortcut for creating an item seems to work but the other two dont for me.
Using the quick select when one of the options are selected and you click outside of the model acts as if you selected option.
image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/components/global/QuickMenu/Input.vue (1)

Line range hint 1-116: Consider additional security measures for the quick menu component.

While reviewing the component, I noticed some potential security considerations:

  1. Input validation: Consider sanitizing user input before using it in the filtering logic to prevent potential XSS attacks through action text.
  2. Action execution safeguards: The quick select feature (single-character shortcuts) could benefit from an additional confirmation step for destructive actions.
  3. Rate limiting: Consider adding debounce to prevent rapid-fire action execution.

Here's a suggested implementation for input sanitization:

  const filteredActions = computed(() => {
-   const searchTerm = inputValue.value.toLowerCase();
+   const searchTerm = sanitizeInput(inputValue.value.toLowerCase());
    return (props.actions || []).filter(action => {
      return action.text.toLowerCase().includes(searchTerm) || action.shortcut?.includes(searchTerm);
    });
  });

+ function sanitizeInput(input: string): string {
+   // Remove potentially dangerous characters while preserving legitimate search terms
+   return input.replace(/[<>'"]/g, '');
+ }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f511e3 and 026fbae.

📒 Files selected for processing (2)
  • frontend/components/Item/CreateModal.vue (2 hunks)
  • frontend/components/global/QuickMenu/Input.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/components/Item/CreateModal.vue
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: build (linux/arm/v7)
  • GitHub Check: build (linux/arm/v7)
  • GitHub Check: Frontend and End-to-End Tests / Integration Tests
  • GitHub Check: build (linux/arm64)
  • GitHub Check: build (linux/arm64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (1)
frontend/components/global/QuickMenu/Input.vue (1)

84-85: LGTM! The focus handling change improves security.

The simplified focus handling logic that only clears input on blur is better than automatically revealing actions, as it reduces the risk of unintended action execution.

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.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
frontend/components/Location/CreateModal.vue (1)

Line range hint 108-143: Enhance security and error handling in create function.

The current implementation has several security and reliability concerns:

  1. No rate limiting on form submission
  2. Basic error handling
  3. Unconstrained navigation

Consider implementing:

  1. Rate limiting for form submissions
  2. More detailed error handling
  3. Navigation validation
+ const SUBMIT_COOLDOWN = 1000; // 1 second
+ let lastSubmitTime = 0;
+
  async function create(close = true) {
    if (loading.value) {
      toast.error("Already creating a location");
      return;
    }
+   
+   // Rate limiting
+   const now = Date.now();
+   if (now - lastSubmitTime < SUBMIT_COOLDOWN) {
+     toast.error("Please wait before submitting again");
+     return;
+   }
+   lastSubmitTime = now;
+
    loading.value = true;

    if (shift.value) {
      close = false;
    }

    const { data, error } = await api.locations.create({
      name: form.name,
      description: form.description,
      parentId: form.parent ? form.parent.id : null,
    });

    if (error) {
      loading.value = false;
-     toast.error("Couldn't create location");
+     // More detailed error handling
+     toast.error(`Failed to create location: ${error.message || 'Unknown error'}`);
+     return;
    }

    if (data) {
+     // Validate data.id before navigation
+     if (!data.id || typeof data.id !== 'string') {
+       toast.error("Invalid response from server");
+       loading.value = false;
+       return;
+     }
      toast.success("Location created");
    }
    reset();

    if (close) {
      modal.value = false;
-     navigateTo(`/location/${data.id}`);
+     // Safe navigation with validated id
+     if (data?.id) {
+       navigateTo(`/location/${encodeURIComponent(data.id)}`);
+     }
    }
  }
🛑 Comments failed to post (2)
frontend/components/Location/CreateModal.vue (2)

100-105: ⚠️ Potential issue

Enhance route parameter validation for security.

The current implementation directly uses route parameters without proper validation, which could lead to security vulnerabilities.

Consider this safer implementation:

  const locationId = computed(() => {
    if (route.fullPath.includes("/location/")) {
-     return route.params.id;
+     const id = route.params.id;
+     // Validate id format (assuming UUID format)
+     return typeof id === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)
+       ? id
+       : null;
    }
    return null;
  });
📝 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 locationId = computed(() => {
    if (route.fullPath.includes("/location/")) {
      const id = route.params.id;
      // Validate id format (assuming UUID format)
      return typeof id === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)
        ? id
        : null;
    }
    return null;
  });

62-79: 🛠️ Refactor suggestion

Improve timing and cleanup in watch handler.

The current implementation has several potential issues:

  1. The 50ms timeout is arbitrary and might not be reliable across different devices
  2. Missing cleanup for the timeout on component unmount
  3. Potential race condition between timeout and location finding

Consider this improved implementation:

+ const timeoutRef = ref<number | null>(null);
+ 
  watch(
    () => modal.value,
    open => {
      if (open) {
-       useTimeoutFn(() => {
-         focused.value = true;
-       }, 50);
+       // Clear any existing timeout
+       if (timeoutRef.value) clearTimeout(timeoutRef.value);
+       // Set new timeout and store reference
+       timeoutRef.value = setTimeout(() => {
+         focused.value = true;
+         timeoutRef.value = null;
+       }, 50);

        if (locationId.value) {
          const found = locations.value.find(l => l.id === locationId.value);
          if (found) {
            form.parent = found;
          }
        }
      } else {
        focused.value = false;
+       // Clear timeout on modal close
+       if (timeoutRef.value) {
+         clearTimeout(timeoutRef.value);
+         timeoutRef.value = null;
+       }
      }
    }
  );
+
+ // Cleanup on component unmount
+ onBeforeUnmount(() => {
+   if (timeoutRef.value) {
+     clearTimeout(timeoutRef.value);
+     timeoutRef.value = null;
+   }
+ });
📝 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 timeoutRef = ref<number | null>(null);
  
  watch(
    () => modal.value,
    open => {
      if (open) {
        // Clear any existing timeout
        if (timeoutRef.value) clearTimeout(timeoutRef.value);
        // Set new timeout and store reference
        timeoutRef.value = setTimeout(() => {
          focused.value = true;
          timeoutRef.value = null;
        }, 50);

        if (locationId.value) {
          const found = locations.value.find(l => l.id === locationId.value);
          if (found) {
            form.parent = found;
          }
        }
      } else {
        focused.value = false;
        // Clear timeout on modal close
        if (timeoutRef.value) {
          clearTimeout(timeoutRef.value);
          timeoutRef.value = null;
        }
      }
    }
  );

  // Cleanup on component unmount
  onBeforeUnmount(() => {
    if (timeoutRef.value) {
      clearTimeout(timeoutRef.value);
      timeoutRef.value = null;
    }
  });

@tankerkiller125
Copy link
Contributor

Sorry for the broken Docker builds, we're working on sorting that out. However, the linting issues should be correct.

@Blackshadow8910
Copy link
Contributor Author

Ok, the aforementioned problems should be fixed now. I didn't have any problems with the shortcuts though, could you check again to see if they work for you?

mcarbonne added a commit to mcarbonne/homebox that referenced this pull request Jan 20, 2025
@mcarbonne
Copy link
Contributor

I did the test and this PR do fix #483 👍 (@Blackshadow8910 FYI)

@tonyaellie
Copy link
Collaborator

Hovering the mouse outside the box and then using the arrow keys to select an option and then clicking outside still causes it to be opened.
bug with clicking.webm

On small screens and mobile the dropdown still seems to be cut off.
image

Ctrl + Shift + # doesnt work for me but ctrl + shift + 3 does as im using a uk keyboard, maybe labelling it as ctr + shift + number would be better?

@Blackshadow8910
Copy link
Contributor Author

Ahhh i had the menu working but then changed a line for some reason and broke it again

The shortcuts should be labelled better now, sorry, I forgot other keyboard layouts exist. They should be just shift + number though, and I'm surprised that adding control doesn't trigger any browser shortcuts. Would it be better if I changed it to ctrl+shift+1/2/3?

I also had to do a dumb thing to get the modal to move to the top on small screens since this version of daisyui doesn't have modal-top and updating it seems to break the ui. Is that okay?

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/components/Base/Modal.vue (1)

91-109: Optimize transform properties for better maintainability.

The transform properties can be simplified using Tailwind's transform utilities.

-      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
-        skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+      @apply translate-y-10 scale-100;
🛑 Comments failed to post (1)
frontend/layouts/default.vue (1)

205-211: ⚠️ Potential issue

Validate action execution context.

Add validation to ensure actions are only executed in appropriate contexts:

 whenever(keys[shortcutKeycode], () => {
-  if (activeElement.value?.tagName !== "INPUT") {
+  if (activeElement.value?.tagName !== "INPUT" && 
+      document.visibilityState === "visible" && 
+      !modals.quickMenu) {
     option.action();
   }
 });
📝 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.

    if (option?.shortcut) {
      const shortcutKeycode = option.shortcut.replace(/([0-9])/, "digit$&");
      whenever(keys[shortcutKeycode], () => {
        if (activeElement.value?.tagName !== "INPUT" && 
            document.visibilityState === "visible" && 
            !modals.quickMenu) {
          option.action();
        }
      });

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/components/Base/Modal.vue (2)

4-7: Consider XSS implications of dynamic class binding.

The dynamic class binding using props.modalTop is secure as it's a boolean value. However, for future maintainability, consider using a computed property to handle the class logic.

+ const modalPositionClass = computed(() => ({
+   'modal-bottom': !props.modalTop,
+   'modal-top': props.modalTop
+ }))

Then in template:

- :class="{ 'modal-bottom': !props.modalTop, 'modal-top': props.modalTop }"
+ :class="modalPositionClass"
🧰 Tools
🪛 GitHub Actions: Pull Request CI

[warning] 6-6: Classname 'modal-top' is not a Tailwind CSS class!


Line range hint 1-109: Security Recommendations:

  1. Consider adding CSRF protection for modal actions
  2. Implement rate limiting for modal open/close events
  3. Sanitize any dynamic content rendered in modal slots
  4. Add aria-labels and roles for better accessibility
🛑 Comments failed to post (1)
frontend/components/Base/Modal.vue (1)

91-109: 🛠️ Refactor suggestion

Address Tailwind class warning and potential style injection.

The pipeline reports that 'modal-top' is not a Tailwind class. Consider:

  1. Using Tailwind's built-in classes instead of custom CSS
  2. If custom CSS is needed, add the class to your Tailwind config

Additionally, the transform properties should be sanitized to prevent potential CSS injection attacks if these values become dynamic in the future.

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/components/Base/Modal.vue (1)

Line range hint 1-110: Add security headers for modal protection

Since this is a modal component that could potentially display sensitive information, ensure that proper security headers are set in your application:

  • X-Frame-Options: to prevent clickjacking
  • Content-Security-Policy: to control resource loading

Consider implementing these headers in your Nuxt.js configuration:

// nuxt.config.js
export default {
  render: {
    csp: {
      policies: {
        'frame-ancestors': ["'none'"],
      }
    }
  }
}
🧰 Tools
🪛 GitHub Actions: Pull Request CI

[warning] 48-48: '<script setup lang=ts>' should be above '<style lang=css scoped>' on line 28

🛑 Comments failed to post (2)
frontend/components/Base/Modal.vue (2)

28-28: 🛠️ Refactor suggestion

Fix linting issues

The pipeline indicates two issues:

  1. The <script> tag should be placed above the <style> tag
  2. Missing line break at the end of file

Apply these changes to fix the linting issues:

-<style lang="css" scoped>
-  /* ... style content ... */
-</style>
-
-<script setup lang="ts">
+<script setup lang="ts">
   /* ... script content ... */
 </script>
+
+<style lang="css" scoped>
+  /* ... style content ... */
+</style>
+

Also applies to: 110-110


86-90: 🛠️ Refactor suggestion

Add cleanup for click outside handler

The click outside handler should be cleaned up when the component is unmounted to prevent memory leaks.

+import { onUnmounted } from 'vue';

+let stopClickOutside: (() => void) | undefined;

 if (props.clickOutsideToClose) {
-  onClickOutside(modalBox, () => {
+  stopClickOutside = onClickOutside(modalBox, () => {
     close();
   });
 }

+onUnmounted(() => {
+  if (stopClickOutside) {
+    stopClickOutside();
+  }
+});
📝 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 { onUnmounted } from 'vue';

let stopClickOutside: (() => void) | undefined;

if (props.clickOutsideToClose) {
  stopClickOutside = onClickOutside(modalBox, () => {
    close();
  });
}

onUnmounted(() => {
  if (stopClickOutside) {
    stopClickOutside();
  }
});

Copy link
Collaborator

@tonyaellie tonyaellie left a comment

Choose a reason for hiding this comment

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

Leave it as shift+1/2/3 I think, i was just didnt realist ctrl was not necesary.
We really need to update to a new version of daisyui soon but this is fine for now I think.
Just need task ui:fix and should be good for merge.

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/layouts/default.vue (1)

183-183: Consider notifying users about shortcut changes.

The change from Control to Shift modifiers should be communicated to users, especially for those who frequently use keyboard shortcuts.

Consider adding a one-time notification or tooltip to inform users about the shortcut changes:

+ const hasSeenShortcutNotice = ref(localStorage.getItem('shortcutNoticeShown'))
+ // Add to mounted hook or appropriate lifecycle event
+ if (!hasSeenShortcutNotice.value) {
+   // Show notification about shortcut changes
+   localStorage.setItem('shortcutNoticeShown', 'true')
+ }

Also applies to: 191-191, 199-199

🛑 Comments failed to post (1)
frontend/layouts/default.vue (1)

207-212: ⚠️ Potential issue

Add security measures to shortcut handling.

The current implementation has potential security considerations:

  1. The regex replacement could be vulnerable to injection
  2. Lacks error handling for action execution

Consider implementing these security measures:

 dropdown.forEach(option => {
   if (option?.shortcut) {
-    const shortcutKeycode = option.shortcut.replace(/([0-9])/, "digit$&");
+    // Validate shortcut format
+    if (!/^Shift\+[1-9]$/.test(option.shortcut)) {
+      console.error('Invalid shortcut format:', option.shortcut);
+      return;
+    }
+    const shortcutKeycode = option.shortcut.replace(/([1-9])/, "digit$1");
     whenever(keys[shortcutKeycode], () => {
       if (activeElement.value?.tagName !== "INPUT") {
-        option.action();
+        try {
+          option.action();
+        } catch (error) {
+          console.error('Error executing shortcut action:', error);
+        }
       }
     });
   }
 });

Committable suggestion skipped: line range outside the PR's diff.

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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
frontend/components/Base/Modal.vue (3)

4-8: Consider using computed properties for reactive template bindings.

While the current implementation works, accessing props directly in the template via props.modalTop could lead to reactivity issues. Additionally, the class binding could be more defensive against potential runtime errors.

Consider this improvement:

-    <div
-      class="modal overflow-visible sm:modal-middle"
-      :class="{ 'modal-bottom': !props.modalTop }"
-      :modal-top="props.modalTop"
-    >
+    <div
+      class="modal overflow-visible sm:modal-middle"
+      :class="{ 'modal-bottom': !isModalTop }"
+      :modal-top="isModalTop"
+    >

<script setup>
+ const isModalTop = computed(() => props.modalTop ?? false)
</script>

48-55: Add JSDoc documentation for new props.

The new props lack documentation explaining their purpose and usage, unlike the existing readonly prop which has clear documentation.

Add documentation for clarity:

+    /**
+     * When true, clicking outside the modal will close it.
+     * Use this for non-critical modals where data loss is not a concern.
+     */
     clickOutsideToClose: {
       type: Boolean,
       default: false,
     },
+    /**
+     * Controls the modal's vertical positioning.
+     * When true, positions the modal at the top of the screen on mobile devices.
+     */
     modalTop: {
       type: Boolean,
       default: false,
     },

93-109: Simplify transform properties and use design system variables.

The mobile styles use hardcoded values and complex transform properties that could be simplified.

Consider these improvements:

- @media (max-width: 640px) {
+ @media (max-width: theme('screens.sm')) {
   .modal[modal-top] {
     align-items: start;
   }

   .modal[modal-top] :where(.modal-box) {
     max-width: none;
-    --tw-translate-y: 2.5rem /* 40px */;
-    --tw-scale-x: 1;
-    --tw-scale-y: 1;
-    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
-      skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+    transform: translateY(2.5rem);
     width: 100%;
     border-top-left-radius: 0px;
     border-top-right-radius: 0px;
   }
 }
🛑 Comments failed to post (1)
frontend/components/Base/Modal.vue (1)

66-70: ⚠️ Potential issue

Add cleanup for click outside handler and consider security implications.

The click outside handler setup lacks cleanup and could have security implications if the modal contains sensitive information.

Consider these security and performance improvements:

+ const clickOutsideStop = ref()

- if (props.clickOutsideToClose) {
-   onClickOutside(modalBox, () => {
-     close();
-   });
- }
+ watchEffect((onCleanup) => {
+   if (props.clickOutsideToClose && modal.value) {
+     const { stop } = onClickOutside(modalBox, () => {
+       // Add a small delay to prevent accidental clicks when the modal opens
+       setTimeout(() => {
+         if (modal.value) close();
+       }, 100);
+     });
+     clickOutsideStop.value = stop;
+     onCleanup(() => {
+       clickOutsideStop.value?.();
+     });
+   }
+ });

Also, consider adding a security warning in the documentation:

/**
 * @security When using clickOutsideToClose with modals containing sensitive information,
 * ensure proper confirmation dialogs are in place to prevent accidental data loss
 */

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/layouts/default.vue (1)

209-214: Consider implementing rate limiting for shortcuts

While the input field protection is good, consider implementing rate limiting to prevent shortcut abuse.

 dropdown.forEach(option => {
   if (option?.shortcut) {
     const shortcutKeycode = option.shortcut.replace(/([0-9])/, "digit$&");
+    const rateLimiter = useThrottleFn(() => {
+      if (activeElement.value?.tagName !== "INPUT") {
+        option.action();
+      }
+    }, 500);
     whenever(keys[shortcutKeycode], () => {
-      if (activeElement.value?.tagName !== "INPUT") {
-        option.action();
-      }
+      rateLimiter();
     });
   }
 });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 60affde and 37315f1.

📒 Files selected for processing (1)
  • frontend/layouts/default.vue (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (linux/arm/v7)
  • GitHub Check: build (linux/arm/v7)
  • GitHub Check: build (linux/arm64)
  • GitHub Check: build (linux/arm64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (4)
frontend/layouts/default.vue (4)

75-79: LGTM! Accessible keyboard shortcut display

The keyboard shortcut display implementation follows accessibility best practices using semantic HTML.


282-282: LGTM! Well-scoped quick menu actions

The quick menu actions are properly scoped and use Vue Router for navigation, maintaining security boundaries.


152-159: LGTM! Good input field protection

The active element tracking prevents shortcut execution while typing, which is a good security practice. Consider extending this protection to other sensitive elements like contenteditable.

✅ Verification successful

✅ Current implementation provides sufficient protection

The active element tracking already covers all editable elements in the codebase. All v-html directives are properly sanitized with DOMPurify, and there are no contenteditable elements that would require additional protection.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other potentially sensitive elements that might need protection
rg -A 3 "contenteditable|designMode|isContentEditable" --type vue

Length of output: 95


Script:

#!/bin/bash
# Find .vue files and search for editable elements
fd -e vue -x rg -l "contenteditable|designMode|isContentEditable|v-html|textarea|editable" {}

# Get more context around matches if found
fd -e vue -x rg -A 3 "contenteditable|designMode|isContentEditable|v-html|textarea|editable" {}

Length of output: 3211


37-40: Consider security implications of version exposure

The version and build information is publicly displayed and linked to GitHub releases. While useful for users, this could help attackers identify vulnerable versions.

Consider:

  1. Limiting version info visibility to authenticated users
  2. Using a security.txt file for responsible disclosure
  3. Implementing a version check API that only indicates if an update is needed
✅ Verification successful

Version information exposure follows security best practices

The current implementation already follows security best practices:

  • Basic version info is available on the landing page to help users stay up-to-date
  • Detailed build information (commit hashes) is only visible in the authenticated layout
  • GitHub release links are only exposed to authenticated users
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if version information is exposed to unauthenticated users
rg -A 3 "version|build.commit" --type vue

Length of output: 71


Script:

#!/bin/bash
# Search for version and build information patterns
rg -l "status\.build\.(version|commit)" 

# Search for authentication-related code near version display
rg -B 5 -A 5 "status\.build\.(version|commit)"

# Find status object initialization/assignment
ast-grep --pattern 'status = $_'

Length of output: 4039


Script:

#!/bin/bash
# Search for status object definition and authentication checks
rg -B 5 -A 5 "defineProps|status\s*=|useAuth" frontend/

# Check for authentication-related composables or utilities
rg -l "auth|isAuthenticated|requireAuth" frontend/

Length of output: 37576

mcarbonne added a commit to mcarbonne/homebox that referenced this pull request Jan 26, 2025
@tonyaellie tonyaellie merged commit fca7d24 into sysadminsmedia:main Jan 26, 2025
3 of 11 checks passed
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.

Fill the parent location field when creating a location being inside another location
4 participants