Skip to content

Conversation

@ilong4rennes
Copy link
Contributor

@ilong4rennes ilong4rennes commented Dec 6, 2024

What:

  • Converted React class components to function components for 48 out of the 49 files are listed in issue Convert React components to functions #571. Updates include replacing class-based lifecycle methods with React hooks and ensuring the components maintain the same behavior as before.
  • The remaining file, admin/src/Models/CollectionNavigation.js, contains a CollectionNavigation class that serves as a base class for multiple other components. To maintain consistency and avoid introducing incomplete changes, we opted to leave this class unchanged for now. After investigating related dependencies, a new issue has been created to address this component separately (Refactor CollectionNavigation and Related Components to Function Components #582 ).

Why:

Modernizing the codebase to align with current React best practices. Function components are more concise, easier to read, and enable the use of modern React features like hooks.

Testing:

  • ESLint passes during make test
  • Manual Testing UI: Verified the UI functionality of all updated components. Screenshots of the results are included in the related PRs, available at this link.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new functional components for various forms and inputs, enhancing performance and maintainability.
    • Added context management for group and member data, improving state sharing across components.
  • Bug Fixes

    • Streamlined state management and subscription handling across multiple components, ensuring better performance and responsiveness.
  • Refactor

    • Refactored multiple components from class-based to functional components using React hooks, simplifying code structure and enhancing readability.
    • Updated component signatures and export statements to align with the new functional component structure.

These changes improve the overall user experience by making the application more responsive and easier to maintain.

@emanuelen5 emanuelen5 linked an issue Dec 7, 2024 that may be closed by this pull request
49 tasks
@emanuelen5
Copy link
Member

emanuelen5 commented Dec 7, 2024

The ruff test failed on main, which is why it failed on this PR as well. However, I've fixed it now on main (https://github.com/makerspace/makeradmin/compare/097ee72dce2585a503c942b19eaf10a84094e752%5E..fe136f9aa9873118307998495c2efccf6cb1677d), so you can merge from main to get that passing.

@emanuelen5 emanuelen5 self-requested a review December 7, 2024 19:54
@emanuelen5 emanuelen5 added the legacy Old stuff that might need updating or refactoring label Dec 7, 2024
@emanuelen5
Copy link
Member

emanuelen5 commented Dec 7, 2024

Thank you very much for the contributions!

I haven't looked through all pages, but I noted that there are a few regressions:

@HalfVoxel
Copy link
Contributor

What a great contribution <3! Amazing work!

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

🧹 Outside diff range and nitpick comments (2)
admin/src/Membership/MembershipPeriodsInput.js (1)

98-130: Enhance form UX and accessibility

Consider the following improvements:

  1. Add loading state during save operation
  2. Improve accessibility with aria-labels
  3. Add error feedback UI
+    const [isSaving, setIsSaving] = useState(false);
+
     return (
         <form
             className="uk-form"
             onSubmit={(e) => {
                 e.preventDefault();
+                setIsSaving(true);
-                onSave();
+                onSave()
+                    .finally(() => setIsSaving(false));
                 return false;
             }}
         >
             <label className="uk-label" htmlFor="showHistoric">
                 Visa historiska
             </label>
             <input
                 id="showHistoric"
                 className="uk-checkbox"
                 type="checkbox"
+                aria-label="Show historic entries"
                 checked={showHistoric}
                 onChange={(e) => setShowHistoric(e.target.checked)}
             />
             {/* ... */}
             <button
-                disabled={saveDisabled}
+                disabled={saveDisabled || isSaving}
                 className="uk-button uk-button-success uk-float-right"
+                aria-busy={isSaving}
             >
-                <i className="uk-icon-save" /> Spara
+                <i className="uk-icon-save" /> {isSaving ? 'Saving...' : 'Spara'}
             </button>
         </form>
     );
admin/src/Membership/MemberBoxSpans.jsx (1)

17-17: Clarify the use of setItems without items in state

You are destructuring useState as const [, setItems] = useState([]);, effectively ignoring the items state variable. If items is not needed elsewhere in the component, consider the following:

  • Remove the state variable altogether if it's not used.
  • Add a comment explaining that setItems is used to trigger re-renders when the collection changes.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4604cd1 and eaccc32.

📒 Files selected for processing (2)
  • admin/src/Membership/MemberBoxSpans.jsx (2 hunks)
  • admin/src/Membership/MembershipPeriodsInput.js (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
admin/src/Membership/MembershipPeriodsInput.js

[error] 67-74: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (5)
admin/src/Membership/MembershipPeriodsInput.js (2)

65-75: Refactor arrow function assignment

The assignment within the arrow function makes the code less readable and was flagged by static analysis.

🧰 Tools
🪛 Biome (1.9.4)

[error] 67-74: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1-134: Verify fixes for reported UI regressions

The reported issues with infinite network requests and blank pages could be related to the identified problems in state management and effect dependencies. Let's verify the component's behavior:

🧰 Tools
🪛 Biome (1.9.4)

[error] 67-74: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Membership/MemberBoxSpans.jsx (3)

29-44: Refactor useEffect to separate concerns and add missing dependencies

This comment is similar to a previous review comment.

The current useEffect combines data fetching and lacks necessary dependencies, which can lead to unexpected behaviors. Consider the following refactoring:

  1. Memoize fetchPendingActions using useCallback to prevent unnecessary re-creation of the function on every render.
  2. Separate concerns by splitting into two useEffect hooks: one for fetching data and another for handling side effects.
  3. Add fetchPendingActions to the dependency array to ensure the effect is triggered appropriately.

Apply the following changes:

+const fetchPendingActions = React.useCallback(() => {
+    get({ url: `/membership/member/${memberId}/pending_actions` }).then(
+        (r) => {
+            const sumPendingLabaccessDays = r.data.reduce(
+                (acc, value) => {
+                    if (value.action.action === ADD_LABACCESS_DAYS)
+                        return acc + value.action.value;
+                    return acc;
+                },
+                0,
+            );
+            setPendingLabaccessDays(sumPendingLabaccessDays);
+        },
+    );
+}, [memberId]);

-useEffect(() => {
-    get({ url: `/membership/member/${memberId}/pending_actions` }).then(
-        (r) => {
-            const sum_pending_labaccess_days = r.data.reduce(
-                (acc, value) => {
-                    if (value.action.action === ADD_LABACCESS_DAYS)
-                        return acc + value.action.value;
-                    return acc;
-                },
-                0,
-            );
-            setPendingLabaccessDays(sum_pendingLabaccessDays);
-        },
-    );
-}, [memberId]);
+useEffect(() => {
+    fetchPendingActions();
+}, [fetchPendingActions]);

47-54: Include collectionRef in the dependency array of useEffect

This is similar to a previous review comment.

The useEffect hook for subscribing to collection changes has an empty dependency array, which can cause issues if collectionRef changes. Include collectionRef.current in the dependencies to ensure proper subscription management.

Apply this diff:

 useEffect(() => {
     const unsubscribe = collectionRef.current.subscribe(({ items }) => {
         setItems(items);
     });
     return () => {
         unsubscribe();
     };
-}, []);
+}, [collectionRef]);

115-120: Improve accessibility for the delete button

This comment is a duplicate of a previous review comment.

To enhance accessibility, add the href, role, and aria-label attributes to the delete button. This ensures that assistive technologies can interpret the button correctly.

Apply this diff:

 <a
     onClick={() => deleteItem(item)}
     className="removebutton"
+    href="#"
+    role="button"
+    aria-label={`Delete span ${item.id}`}
 >
     <i className="uk-icon-trash" />
 </a>

Comment on lines +12 to +18
const categoryPeriodsListRef = useRef([
new CategoryPeriods({ category: "labaccess" }),
new CategoryPeriods({ category: "membership" }),
new CategoryPeriods({ category: "special_labaccess" }),
]);

const categoryPeriodsList = categoryPeriodsListRef.current;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider using useState instead of useRef for categoryPeriodsList

The current implementation using useRef for categoryPeriodsList creates mutable state outside of React's control. This could be contributing to the infinite network requests mentioned in the PR feedback. Consider using useState instead:

-    const categoryPeriodsListRef = useRef([
-        new CategoryPeriods({ category: "labaccess" }),
-        new CategoryPeriods({ category: "membership" }),
-        new CategoryPeriods({ category: "special_labaccess" }),
-    ]);
-
-    const categoryPeriodsList = categoryPeriodsListRef.current;
+    const [categoryPeriodsList] = useState(() => [
+        new CategoryPeriods({ category: "labaccess" }),
+        new CategoryPeriods({ category: "membership" }),
+        new CategoryPeriods({ category: "special_labaccess" }),
+    ]);

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

Comment on lines +83 to +93
// post({
// url: `/webshop/member/${member_id}/ship_labaccess_orders`,
// expectedDataStatus: "ok",
// });
post({
url: `/webshop/member/${member_id}/ship_labaccess_orders`,
headers: { "Content-Type": "application/json" },
payload: {
/* any required data */
},
expectedDataStatus: "ok",
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up commented code and improve error handling

  1. Remove the commented-out code block
  2. Add proper error handling for the POST request
-            // post({
-            //     url: `/webshop/member/${member_id}/ship_labaccess_orders`,
-            //     expectedDataStatus: "ok",
-            // });
             post({
                 url: `/webshop/member/${member_id}/ship_labaccess_orders`,
                 headers: { "Content-Type": "application/json" },
                 payload: {
-                    /* any required data */
+                    member_id,
+                    spans: addSpans.map(s => s.id)
                 },
                 expectedDataStatus: "ok",
+            }).catch(error => {
+                console.error('Failed to ship labaccess orders:', error);
+                // Add appropriate error handling UI feedback
             });

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

Comment on lines +27 to +47
useEffect(() => {
const unsubscribes = [];

// Subscribe to spans updates
unsubscribes.push(
spans.subscribe(({ items }) => {
categoryPeriodsList.forEach((periods) =>
periods.replace(filterPeriods(items, periods.category)),
);
}),
);
this.categoryPeriodsList.forEach((cp) => {
this.unsubscribe.push(
cp.subscribe(() =>
this.setState({ saveDisabled: !this.canSave() }),
),
);
});
}

componentWillUnmount() {
this.unsubscribe.forEach((u) => u());
}
// Subscribe to categoryPeriods updates
categoryPeriodsList.forEach((cp) => {
unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave())));
});

render() {
const { showHistoric, saveDisabled } = this.state;
const { member_id, spans } = this.props;
return () => {
unsubscribes.forEach((u) => u());
};
}, [spans, categoryPeriodsList, canSave]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize useEffect dependencies and subscription management

The current implementation has several potential issues:

  1. Including categoryPeriodsList in the dependency array could cause unnecessary effect reruns
  2. The subscription setup could benefit from better error handling
     useEffect(() => {
         const unsubscribes = [];
+        let isSubscribed = true;  // Add mounted check
 
         // Subscribe to spans updates
         unsubscribes.push(
             spans.subscribe(({ items }) => {
+                if (!isSubscribed) return;  // Prevent updates after unmount
                 categoryPeriodsList.forEach((periods) =>
                     periods.replace(filterPeriods(items, periods.category)),
                 );
             }),
         );
 
         // Subscribe to categoryPeriods updates
         categoryPeriodsList.forEach((cp) => {
+            if (!isSubscribed) return;  // Prevent updates after unmount
             unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave())));
         });
 
         return () => {
+            isSubscribed = false;
             unsubscribes.forEach((u) => u());
         };
-    }, [spans, categoryPeriodsList, canSave]);
+    }, [spans, canSave]); // Remove categoryPeriodsList from dependencies
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const unsubscribes = [];
// Subscribe to spans updates
unsubscribes.push(
spans.subscribe(({ items }) => {
categoryPeriodsList.forEach((periods) =>
periods.replace(filterPeriods(items, periods.category)),
);
}),
);
this.categoryPeriodsList.forEach((cp) => {
this.unsubscribe.push(
cp.subscribe(() =>
this.setState({ saveDisabled: !this.canSave() }),
),
);
});
}
componentWillUnmount() {
this.unsubscribe.forEach((u) => u());
}
// Subscribe to categoryPeriods updates
categoryPeriodsList.forEach((cp) => {
unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave())));
});
render() {
const { showHistoric, saveDisabled } = this.state;
const { member_id, spans } = this.props;
return () => {
unsubscribes.forEach((u) => u());
};
}, [spans, categoryPeriodsList, canSave]);
useEffect(() => {
const unsubscribes = [];
let isSubscribed = true; // Add mounted check
// Subscribe to spans updates
unsubscribes.push(
spans.subscribe(({ items }) => {
if (!isSubscribed) return; // Prevent updates after unmount
categoryPeriodsList.forEach((periods) =>
periods.replace(filterPeriods(items, periods.category)),
);
}),
);
// Subscribe to categoryPeriods updates
categoryPeriodsList.forEach((cp) => {
if (!isSubscribed) return; // Prevent updates after unmount
unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave())));
});
return () => {
isSubscribed = false;
unsubscribes.forEach((u) => u());
};
}, [spans, canSave]); // Remove categoryPeriodsList from dependencies

Comment on lines +33 to +41
const sum_pending_labaccess_days = r.data.reduce(
(acc, value) => {
if (value.action.action === ADD_LABACCESS_DAYS)
return acc + value.action.value;
return acc;
},
0,
);
setPendingLabaccessDays(sum_pending_labaccess_days);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent variable naming convention

The variable sum_pending_labaccess_days uses snake_case, whereas the rest of the codebase predominantly uses camelCase. For consistency and readability, it's recommended to use camelCase for variable names in JavaScript.

Apply this diff to rename sum_pending_labaccess_days to sumPendingLabaccessDays:

 const sumPendingLabaccessDays = r.data.reduce(
     (acc, value) => {
         if (value.action.action === ADD_LABACCESS_DAYS)
             return acc + value.action.value;
         return acc;
     },
     0,
 );
-setPendingLabaccessDays(sum_pending_labaccess_days);
+setPendingLabaccessDays(sumPendingLabaccessDays);

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.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
admin/src/Sales/OrderShow.js (1)

37-46: Optimize the subscription callback with useCallback

The subscription callback should be memoized to prevent unnecessary re-renders and potential subscription churn.

Consider applying this optimization:

+ const handleOrderUpdate = useCallback(() => {
+     const { member_id } = order;
+     setMemberId(member_id);
+ }, [order]);

  useEffect(() => {
-     const unsubscribe = order.subscribe(() => {
-         const { member_id } = order;
-         setMemberId(member_id);
-     });
+     const unsubscribe = order.subscribe(handleOrderUpdate);

      return () => {
          unsubscribe();
      };
- }, [order]);
+ }, [order, handleOrderUpdate]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between eaccc32 and ce5f2b0.

📒 Files selected for processing (1)
  • admin/src/Sales/OrderShow.js (1 hunks)
🔇 Additional comments (4)
admin/src/Sales/OrderShow.js (4)

1-11: LGTM! Clean imports and component declaration

The conversion to a functional component follows React best practices with proper hook imports and clean prop destructuring.


54-56: Handle potential null value for memberId to prevent invalid links

If memberId is null or undefined when the component first renders, the <Link> component will generate an invalid URL.


79-79: Prevent potential division by zero when calculating item price

The price calculation could result in a division by zero error if item.count is zero.


61-89: Verify CollectionTable component integration

Let's verify that the CollectionTable component properly handles the collection props and row rendering.

Also applies to: 93-114

✅ Verification successful

CollectionTable integration is properly implemented

The implementation in OrderShow.js correctly uses the CollectionTable component, matching the expected props interface and usage patterns found across the codebase:

  • The required props are properly provided:
    • collection={orderRows} - for data source
    • columns array with correct structure
    • emptyMessage for empty state
    • rowComponent with correct render implementation

The component usage aligns with the standard patterns seen in 25+ other files across the codebase, and the table structure (headers, rows, currency formatting) follows the established conventions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check CollectionTable implementation and usage patterns

# Find CollectionTable implementation
echo "Searching for CollectionTable implementation..."
ast-grep --pattern 'export $_ CollectionTable' -f js,jsx,ts,tsx

# Find other usages of CollectionTable for comparison
echo "Searching for CollectionTable usage patterns..."
rg -A 5 'CollectionTable' --type js --type jsx --type tsx

Length of output: 539


Script:

#!/bin/bash
# Let's try a different approach to find CollectionTable implementation and usage

# Find CollectionTable implementation
echo "Searching for CollectionTable implementation..."
ast-grep --pattern 'const CollectionTable = $_'

# Find CollectionTable usage with ripgrep
echo "Searching for CollectionTable usage patterns..."
rg "CollectionTable" -A 5

# Find potential files containing CollectionTable
echo "Finding files that might contain CollectionTable..."
fd "CollectionTable" --type f

Length of output: 48464

Comment on lines +70 to +88
rowComponent={({ item }) => (
<tr>
<td>
<Link to={`/sales/product/${item.product_id}`}>
{item.name}
</Link>
</td>
<td className="uk-text-right">
<Currency
value={(100 * item.amount) / item.count}
/>{" "}
kr
</td>
<td>{item.count}</td>
<td className="uk-text-right">
<Currency value={100 * item.amount} /> kr
</td>
</tr>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add key props to table row components

The row components passed to CollectionTable should have key props for optimal rendering performance.

Apply these changes:

- rowComponent={({ item }) => (
+ rowComponent={({ item, key }) => (
-   <tr>
+   <tr key={key}>

This change should be applied to both CollectionTable instances.

Also applies to: 102-113

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

🧹 Outside diff range and nitpick comments (3)
admin/src/Messages/MessageAdd.js (2)

14-19: Add error handling and consider i18n

The save operation lacks error handling and uses hardcoded Swedish text. Consider:

  1. Adding error handling
  2. Implementing internationalization for messages
 const onSend = () => {
     message.save().then(() => {
         router.push("/messages");
         notifySuccess("Ditt meddelande har skickats");
-    });
+    })
+    .catch((error) => {
+        notifyError("Failed to send message");
+        console.error("Failed to save message:", error);
+    });
 };

21-30: Add prop-types and document MessageForm props

The component would benefit from proper prop-types validation and documentation of the MessageForm props interface.

+import PropTypes from 'prop-types';
+
 function MessageAdd(props) {
     // ... component logic ...
 }
+
+MessageAdd.propTypes = {
+    router: PropTypes.shape({
+        push: PropTypes.func.isRequired,
+    }).isRequired,
+};
+
+// Document MessageForm props interface
+/**
+ * @typedef {Object} MessageFormProps
+ * @property {boolean} recipientSelect - Whether to show recipient selection
+ * @property {Message} message - Message instance to edit
+ * @property {Function} onSave - Callback when save is requested
+ */
admin/src/Messages/MessageShow.js (1)

27-55: Consider optimizing the update handler

The update handler could be memoized to prevent unnecessary recreations and potential race conditions.

Consider this optimization:

+    const updateMessage = useMemo(() => () => {
-    const updateMessage = () => {
         setMessage({
             body: messageInstance.body,
             // ... other fields
         });
-    };
+    }, [messageInstance]);

     useEffect(() => {
-        // Call the function once to initialize state
         updateMessage();
         
         const unsubscribe = messageInstance.subscribe(updateMessage);
         return () => unsubscribe();
     }, [messageInstance, updateMessage]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6b3976a and d69424c.

📒 Files selected for processing (3)
  • admin/src/Membership/SpanShow.jsx (1 hunks)
  • admin/src/Messages/MessageAdd.js (1 hunks)
  • admin/src/Messages/MessageShow.js (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
admin/src/Messages/MessageShow.js

[error] 96-96: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (7)
admin/src/Messages/MessageAdd.js (1)

14-19: Add cleanup for async operations

The component should handle potential unmounting during the save operation to prevent memory leaks or state updates on unmounted components.

admin/src/Messages/MessageShow.js (4)

1-5: LGTM! Clean imports setup

The imports are well-organized and include all necessary dependencies for the functional component implementation.


93-101: ⚠️ Potential issue

Avoid using dangerouslySetInnerHTML to prevent XSS vulnerabilities.

Using dangerouslySetInnerHTML without sanitization exposes the application to cross-site scripting (XSS) attacks.

The previous review's suggestion to use DOMPurify is still valid and should be implemented to secure the application.

🧰 Tools
🪛 Biome (1.9.4)

[error] 96-96: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


48-54: Verify subscription cleanup across converted components

Given the reported issues with infinite network requests in various pages, ensure that subscription cleanup is working correctly.

Let's check the subscription cleanup pattern across converted components:

✅ Verification successful

Based on the comprehensive search results, I can now generate the final response:

Subscription cleanup is properly implemented across components

The codebase shows consistent and correct implementation of subscription cleanup:

  • All functional components properly use useEffect with cleanup functions
  • Class components correctly implement componentWillUnmount
  • The subscription cleanup pattern follows React best practices:
    • Subscriptions are created in useEffect/componentDidMount
    • Cleanup functions are properly returned/implemented
    • Unsubscribe functions are called in cleanup
    • Dependencies are correctly specified in the dependency arrays
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for subscription cleanup patterns in converted components
rg -A 5 'subscribe.*\(' admin/src/
# Look for useEffect cleanup
ast-grep --pattern 'useEffect(() => {
  $$$
  return () => {
    $$$
  };
}, [$$$]);'

Length of output: 51302


6-25: 🛠️ Refactor suggestion

Add error handling for message retrieval

The component should handle cases where the message retrieval fails or the message doesn't exist. Consider adding error state and handling.

Let's check if there are any error boundaries or error handling patterns in the codebase:

Consider implementing error handling:

 function MessageShow(props) {
     const { id } = props.match.params;
+    const [error, setError] = useState(null);
     const messageInstance = useMemo(() => {
+        try {
             return Message.get(id);
+        } catch (err) {
+            setError(err);
+            return null;
+        }
     }, [id]);
admin/src/Membership/SpanShow.jsx (2)

10-18: ⚠️ Potential issue

Fix infinite requests and add error handling

The current implementation could cause infinite network requests and doesn't handle errors properly.

 useEffect(() => {
+    let isSubscribed = true;
+    setIsLoading(true);
+
+    // Initial data fetch
+    spanInstance.fetch().catch(error => {
+        if (isSubscribed) {
+            setError(error);
+            setIsLoading(false);
+        }
+    });
+
     const unsubscribe = spanInstance.subscribe(() => {
+        if (!isSubscribed) return;
         try {
             setData(spanInstance.saved);
+            setIsLoading(false);
+            setError(null);
         } catch (error) {
+            setError(error);
+            console.error('Error updating span data:', error);
         }
     });

     return () => {
+        isSubscribed = false;
         unsubscribe();
     };
-}, [spanInstance]);
+}, [spanInstance, spanInstance.saved]);

1-32: Verify related components and models

Let's check for any related components or models that might affect the SpanShow component's behavior.

✅ Verification successful

The subscription pattern in SpanShow is correctly implemented

The subscription pattern used in SpanShow.jsx follows the same pattern as seen in other components (MemberBoxSpans, SpanList) and properly handles:

  • Subscription setup using useEffect
  • Cleanup through the unsubscribe function
  • Proper dependency tracking with spanInstance
  • Consistent state management with useState

The Span model is correctly imported and used, and the component is properly integrated into the routing system. No issues were found in the implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for related components and models
# Expected: Find components that might affect SpanShow

# Look for components importing SpanShow
rg -l "import.*SpanShow"

# Look for components using the Span model
rg -l "import.*Span.*from.*Models/Span"

# Check for any similar subscription patterns that might have the same issues
ast-grep --pattern 'useEffect(() => {
  $$$
  subscribe($$$)
  $$$
})'

Length of output: 368


Script:

#!/bin/bash
# Let's examine the subscription patterns in the related files

# Check SpanList.jsx for similar subscription patterns
rg "subscribe" admin/src/Membership/SpanList.jsx -B 2 -A 2

# Check MemberBoxSpans.jsx for similar patterns
rg "subscribe" admin/src/Membership/MemberBoxSpans.jsx -B 2 -A 2

# Check the Span model implementation
rg "subscribe" admin/src/Models/Span.js -B 2 -A 2

# Look at the Routes.jsx to see how SpanShow is used
cat admin/src/Membership/Routes.jsx

Length of output: 4383


Script:

#!/bin/bash
# Let's check the Span model implementation and its usage pattern
cat admin/src/Models/Span.js

# Also check MemberBoxSpans to understand the complete usage context
cat admin/src/Membership/MemberBoxSpans.jsx

# And SpanList to see the list implementation
cat admin/src/Membership/SpanList.jsx

Length of output: 13061

Comment on lines +1 to +8
import React, { useMemo } from "react";
import { withRouter } from "react-router";
import MessageForm from "../Components/MessageForm";
import Message from "../Models/Message";
import { notifySuccess } from "../message";
import { withRouter } from "react-router";

class MessageAdd extends React.Component {
constructor(props) {
super(props);
this.message = new Message();
}
function MessageAdd(props) {
const { router } = props;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider upgrading to modern React Router

The use of withRouter HOC and router prop is deprecated in newer versions of React Router. Consider upgrading to use the useNavigate hook instead.

-import { withRouter } from "react-router";
+import { useNavigate } from "react-router-dom";

-function MessageAdd(props) {
-    const { router } = props;
+function MessageAdd() {
+    const navigate = useNavigate();

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

Comment on lines +10 to +11
// Create a new Message instance
const message = useMemo(() => new Message(), []);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace useMemo with useState for mutable state

The useMemo hook is unnecessary here as the Message instance is mutable state. Use useState instead to properly manage the message state and its updates.

-// Create a new Message instance
-const message = useMemo(() => new Message(), []);
+const [message, setMessage] = useState(() => new 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.

Suggested change
// Create a new Message instance
const message = useMemo(() => new Message(), []);
const [message, setMessage] = useState(() => new Message());

Comment on lines +20 to +32
return (
<div>
<h2>Medlemsperiod {data.span_id}</h2>
<dl className="uk-description-list">
{_.keys(data).map((key) => (
<div key={key}>
<dt>{key}:</dt>
<dd>{data[key]}</dd>
</div>
))}
</dl>
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add data validation and field formatting

The current implementation doesn't validate data or format fields properly, which could cause rendering issues.

 return (
     <div>
-        <h2>Medlemsperiod {data.span_id}</h2>
+        <h2>Medlemsperiod {data?.span_id || 'N/A'}</h2>
         <dl className="uk-description-list">
-            {_.keys(data).map((key) => (
+            {Object.entries(data || {}).map(([key, value]) => (
                 <div key={key}>
                     <dt>{key}:</dt>
-                    <dd>{data[key]}</dd>
+                    <dd>{value?.toString() || 'N/A'}</dd>
                 </div>
             ))}
         </dl>
     </div>
 );

Also, consider adding PropTypes for type checking:

import PropTypes from 'prop-types';

SpanShow.propTypes = {
    match: PropTypes.shape({
        params: PropTypes.shape({
            span_id: PropTypes.string.isRequired
        }).isRequired
    }).isRequired
};

Comment on lines +6 to +8
const { span_id } = props.match.params;
const spanInstance = useMemo(() => Span.get(span_id), [span_id]);
const [data, setData] = useState({});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Initialize data state with loading indicator

The empty initial state {} could cause the blank page issue. Consider adding a loading state and error handling.

-const [data, setData] = useState({});
+const [data, setData] = useState(null);
+const [isLoading, setIsLoading] = useState(true);
+const [error, setError] = useState(null);

+if (isLoading) return <div>Loading...</div>;
+if (error) return <div>Error: {error.message}</div>;
+if (!data) return null;

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

Comment on lines +1 to +5
import React, { useEffect, useMemo, useState } from "react";
import * as _ from "underscore";
import Span from "../Models/Span";

class SpanShow extends React.Component {
constructor(props) {
super(props);
const { span_id } = props.match.params;
this.span = Span.get(span_id);
this.state = { data: {} };
}

componentDidMount() {
this.unsubscribe = this.span.subscribe(() =>
this.setState({ data: this.span.saved }),
);
}
function SpanShow(props) {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Missing error handling and incorrect Base model usage causing infinite requests

The verification reveals several critical issues that explain the reported regressions:

  • The Span model extends Base but doesn't implement proper subscription/data fetching methods. The get() method used in SpanShow is inherited from Base but not visible in the implementation.
  • SpanShow component uses spanInstance.saved without checking if the data is actually loaded or if there are any errors.
  • No error handling exists in the subscription effect, which could lead to unhandled promise rejections.
  • The component doesn't handle loading states, potentially causing the blank page issue.

Key locations needing attention:

  • admin/src/Models/Span.js: Missing proper data fetching implementation
  • admin/src/Membership/SpanShow.jsx: Missing error handling and loading states
  • Need to verify Base model implementation for potential subscription issues
🔗 Analysis chain

Verify the Span model import path and implementation

Let's verify the Span model implementation to ensure it properly handles subscriptions and data fetching.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the Span model implementation
# Expected: Find the Span model class with proper subscription and data fetching logic

# Look for the Span model implementation
ast-grep --pattern 'class Span {
  $$$
  get($_) {
    $$$
  }
  $$$
  subscribe($_) {
    $$$
  }
  $$$
}'

# Check for any references to span.saved
rg -A 5 'span\.saved'

Length of output: 133


Script:

#!/bin/bash
# Let's try a broader search for the Span model and its usage

# Find the Span model file
fd "Span.js" -t f

# Look for class definition with a more relaxed pattern
ast-grep --pattern 'class Span'

# Search for Span-related imports
rg "import.*Span.*from"

# Search for Span usage patterns
rg "new Span\(" -A 3

Length of output: 1478


Script:

#!/bin/bash
# Let's examine the Span model implementation and its usage in SpanShow

# Check the Span model implementation
cat admin/src/Models/Span.js

# Look for subscription or data fetching patterns in SpanShow
cat admin/src/Membership/SpanShow.jsx

# Search for any error handling patterns in SpanShow
rg "catch|error" admin/src/Membership/SpanShow.jsx

Length of output: 6554

Copy link
Member

@emanuelen5 emanuelen5 left a comment

Choose a reason for hiding this comment

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

Looks really good! I've reviewed about halfway. I had some comments.

period.subscribe(() => {
this.setState({ start: period.start, end: period.end });
useEffect(() => {
// Synchronize state with period on subscription updates
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

componentWillUnmount() {
this.unsubscribe();
}
// useEffect to handle subscription
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

this.setState({ page, items, loading: false }),
);
}
// Destructure props
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

Copy link
Member

@emanuelen5 emanuelen5 Dec 9, 2024

Choose a reason for hiding this comment

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

Remove all comments in this file

model={keyRef.current}
name="created_at"
title="Skapad"
disabled={true}
Copy link
Member

Choose a reason for hiding this comment

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

disabled does not exist on the DateTimeInput class, you can remove it here and below

</div>
);
if (!group) {
return <div>Group not found</div>; // Handle missing context gracefully
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

Comment on lines +24 to +38
useEffect(() => {
get({ url: "/membership/permission" }).then((data) => {
this.options = data.data;
this.setState({ showOptions: this.filterOptions() });
const fetchedOptions = data.data;
setOptions(fetchedOptions);
setShowOptions(filterOptions(fetchedOptions));
});
}

componentDidMount() {
this.unsubscribe = this.collection.subscribe(() =>
this.setState({ showOptions: this.filterOptions() }),
);
}
const unsubscribe = collection.subscribe(() => {
setShowOptions(filterOptions(options));
});

componentWillUnmount() {
this.unsubscribe();
}
return () => {
unsubscribe();
};
}, []);
Copy link
Member

Choose a reason for hiding this comment

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

Any comment on this? I can't see any problem on http://localhost:8009/membership/groups/1/permissions, but perhaps the Rabbit has a point?

import React from "react";
import Key from "../Models/Key";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useParams, withRouter } from "react-router";
Copy link
Member

Choose a reason for hiding this comment

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

Any comment on this? I think the Rabbit might be correct here as well. In that case, you can remove the withRouter import and its usage on line 107

);
}

export default withRouter(KeyEdit);
Copy link
Member

Choose a reason for hiding this comment

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

Can remove withRouter here as well?

Copy link
Member

Choose a reason for hiding this comment

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

I stopped reviewing here. Will continue later

@emanuelen5
Copy link
Member

emanuelen5 commented Dec 9, 2024

Thank you very much for the contributions!

I haven't looked through all pages, but I noted that there are a few regressions:

* [x]  The member spans page is broken: http://localhost:8009/membership/members/1/spans
  Before:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543480-612f71d3-11e3-4d1d-acf2-9ea425687212.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzQ4MC02MTJmNzFkMy0xMWUzLTRkMWQtYWNmMi05ZWE0MjU2ODcyMTIucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTk5NjVkNWFkMTYxYzY0NDM3YmY1MmZlMDNmMjA3NWU4YTZjMjE2NDgwY2ExMzI3MTY3ZGUyM2UwNzQyODYyNyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.PwZBGC77OpMAG9LkXQoInEqNXsVvrS8U6raVCHE9RqE)
  Now: The page is blank with the following errors in the logs ![image](https://private-user-images.githubusercontent.com/7572427/393542894-702a9354-a904-4de2-9d97-deffc3ba0055.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0Mjg5NC03MDJhOTM1NC1hOTA0LTRkZTItOWQ5Ny1kZWZmYzNiYTAwNTUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9N2U5MWIwZTRmOGI1ZGU3M2VjMTE3MzUzODhjNGYyZmE5YTlmMTQ2NmRiMzYxN2RmMzczMGE0ZDI5YTFiYmUyOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.GvloMzREIiEHuy69c__LaSPkF4yJBp6X_9dAq_SB_T8)

* [x]  The span info page, http://localhost:8009/membership/spans/1, causes infinite network requests, DoS:ing my computer.

* [x]  The span info page, http://localhost:8009/membership/spans/1, does not render correctly - the fields are not filled in.
  Before:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543548-7ad74a50-c4a0-441b-9892-e214e0cfa962.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzU0OC03YWQ3NGE1MC1jNGEwLTQ0MWItOTg5Mi1lMjE0ZTBjZmE5NjIucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZWMwMTQ5YTI0NDNiM2E2NjcyNDk0MDgxOTZlMDc5NTEyNzMzN2Y0ZjhkOGYzOGY2NTE2NGJiZjZlZTEyMzZkMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.73BspnoWEVALK5sZf_7FkRnFB6P5hAtyXIqjLJzRBNI)
  Now:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543617-7bf7eb8f-c5f7-4a70-b902-0b6be99e9787.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzYxNy03YmY3ZWI4Zi1jNWY3LTRhNzAtYjkwMi0wYjZiZTk5ZTk3ODcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDgxOGRlNDRiNDYxNGY3YmJkYmZiM2E0YzRiYTFjYmRkMDFhYzQ2ZGI0N2UxZjA4ODgxNDcyMjE5OTc2YjRlZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.bXfFJ4xCCJwkqdCK1Klliv5K-phRFBOn2zZGmf-F-Z8)

* [x]  The email log page is blank: http://localhost:8009/messages/1
  Before:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543427-1427cf3d-ef8b-49a1-9bfc-74fcc009a593.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzQyNy0xNDI3Y2YzZC1lZjhiLTQ5YTEtOWJmYy03NGZjYzAwOWE1OTMucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTlkMjlhNjVkYWYwNzY2MGU1ZWIwYmQ5YWMyMjA0MGZiODNhMWY0YjNmNTAyYmEwNjcwYzc0NTUwNDk1YzIwZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.RHb_8zXrojR6U1yYnVtF1m_aazmNmUC5t29NqALXsng)
  Now:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543677-774b3b3c-e2de-44f4-8310-606be772207c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzY3Ny03NzRiM2IzYy1lMmRlLTQ0ZjQtODMxMC02MDZiZTc3MjIwN2MucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MDY1NTc4YWUwN2M2ZjA3YWJiYjVkYWRjMzQ3ZWUzOWE3ODFiOTJjMGE1ZTc3M2UwNDE0YzE5MDkwZGM1NzFiOSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.cI66bat1RkyczP6wnlfrUuRtxMcjH8Ua2MZoAcYZJa8)

* [x]  The order page http://localhost:8009/sales/order/1 causes infinite network requests.

* [x]  The order page http://localhost:8009/sales/order/1 does not show the correct member who performed the purchase.
  Before:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543836-16d669ce-87b5-4e91-ae46-add8f731bd22.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0MzgzNi0xNmQ2NjljZS04N2I1LTRlOTEtYWU0Ni1hZGQ4ZjczMWJkMjIucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NmUwMGZmY2UyZDhkNTQ0NTQwMzExNzg5YjMyNmI0Y2NlYzQ3MjMzYzI4MjZjZTA1NzE4NTQyNDU1ZjcyMjc4MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.mObT6k2C6K-AZw2yQng7C-t-5Sf7pgbZ5WmgF1c_phs)
  Now:
  ![image](https://private-user-images.githubusercontent.com/7572427/393543790-a2be89bd-c56b-4e22-ac60-98aa2725d616.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0Mzc5MC1hMmJlODliZC1jNTZiLTRlMjItYWM2MC05OGFhMjcyNWQ2MTYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTdjNmJmYmI1M2NlMjAwYzk5NTYyNmUyYjczOThiMDdjNmM5MjRmMWY0ZTkzZjY1MmVkNzQyMDdhMmVkZDI2ZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.h_d2NluoHxaBUKgN4V-fA615wKdmdnE3p3rmHXYybBQ)

* [x]  The gift-card page, http://localhost:8009/sales/gift-card/1, causes infinite network requests.

* [x]  The gift-card page does not render correctly.
  Before:
  ![image](https://private-user-images.githubusercontent.com/7572427/393544044-7bb3251e-e05c-406b-a1a3-002b0522ed59.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0NDA0NC03YmIzMjUxZS1lMDVjLTQwNmItYTFhMy0wMDJiMDUyMmVkNTkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YmI4YTVlOTI1MWZhZTg5MzFmMWMwM2JkZGNlM2ZhMmE5MGMxZDc3ZThjYjI4NWNkNGQwYjBhYmU5NzM4NmQwZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.onj0WS0OdV0sNgP-S_drMMjuH4wnZqVfmI-LipDjRZA)
  Now:
  ![image](https://private-user-images.githubusercontent.com/7572427/393544010-b29acc3a-80fd-4def-bced-34db47b84451.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM3NzUxNjcsIm5iZiI6MTczMzc3NDg2NywicGF0aCI6Ii83NTcyNDI3LzM5MzU0NDAxMC1iMjlhY2MzYS04MGZkLTRkZWYtYmNlZC0zNGRiNDdiODQ0NTEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMDlUMjAwNzQ3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTlmMzkxMmRkMjkxYjdkMmNhNDYwMTQwOWE2NTUzMzE4MjFlN2IyZjFjMWUyNTU1MjE3Nzg1MGM5MjQ3N2FkYiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.-sfxLOA-Hdr3dckbVb2Kf2rPHI7eFhKXyKcK-HBaXeE)

#581 (comment)

All of these regressions seem fixed now, thank you!

@emanuelen5 emanuelen5 self-requested a review December 9, 2024 22:12
@ilong4rennes
Copy link
Contributor Author

Thank you so much for your detailed feedback, will address these issues later.

@ilong4rennes
Copy link
Contributor Author

Hi, sorry for the delay in response, I've updated the code according to your feedback.

  • deleted comments, unused hooks, removed unnecessary withRouter
  • changed from e.preventDefault(); to return false;
  • turned into a controlled component in RequestPasswordReset.jsx

I have not address these two comments:

  1. rename it to DateTimeDisplay in KeyEdit.jsx, I will make a separate PR later
  2. GroupBoxPermissions.jsx: I'll revisit coderabbit comment and update this later.

Thanks again!

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

🧹 Outside diff range and nitpick comments (17)
admin/src/Components/RequestPasswordReset.jsx (2)

14-29: Consider improving error handling and message consistency.

Two suggestions for improvement:

  1. The error message is in English while the UI is in Swedish. Consider translating it to maintain consistency.
  2. Add error handling for the API call to handle potential failures.

Consider updating the code as follows:

 const submit = (e) => {
     e.preventDefault();

     if (!userIdentification) {
-        showError("You need to fill your email or member number.");
+        showError("Du måste fylla i din e-post eller ditt medlemsnummer.");
         return;
     }

-    auth.requestPasswordReset(userIdentification).then(() => {
-        showSuccess(
-            "Link to password reset will be sent to your email shortly.",
-        );
-        browserHistory.push("/");
-    });
+    auth.requestPasswordReset(userIdentification)
+        .then(() => {
+            showSuccess(
+                "En länk för att återställa lösenordet kommer att skickas till din e-post.",
+            );
+            browserHistory.push("/");
+        })
+        .catch((error) => {
+            showError("Det gick inte att skicka återställningslänken. Försök igen senare.");
+        });
 };

31-79: Consider adding accessibility improvements.

The form structure is clean, but could benefit from accessibility enhancements.

Consider these improvements:

 <div className="uk-form-row">
     <div className="uk-form-icon">
         <i className="uk-icon-user" />
         <input
+            aria-label="Email eller medlemsnummer"
             value={userIdentification}
             onChange={handleInputChange}
             className="uk-form-large uk-form-width-large"
             type="text"
             placeholder="Email/Medlemsnummer"
             autoComplete="username"
         />
     </div>
 </div>
admin/src/Components/Textarea.jsx (1)

9-22: Consider optimizing the useEffect dependencies

The current implementation re-creates the subscription whenever props.model or props.name changes. Consider destructuring these values outside useEffect to avoid unnecessary re-subscriptions.

-    useEffect(() => {
-        const { model, name } = props;
+    const { model, name } = props;
+    useEffect(() => {
         const handleModelChange = () => {
             setValue(model[name] === null ? "" : model[name]);
             setIsDirty(model.isDirty(name));
         };
 
         const unsubscribe = model.subscribe(handleModelChange);
+        handleModelChange(); // Initialize the state
 
         return () => {
             unsubscribe();
         };
-    }, [props.model, props.name]);
+    }, [model, name]);
admin/src/Components/MessageForm.jsx (2)

Line range hint 49-74: Optimize loadOptions function with useCallback

The loadOptions function is recreated on every render. Consider memoizing it with useCallback to improve performance, especially since it's passed as a prop to the Async component.

+    const loadOptions = useCallback((inputValue, callback) => {
-    const loadOptions = (inputValue, callback) => {
         Promise.all([
             // ... existing code ...
         ]);
-    };
+    }, []);

98-101: Simplify recipients onChange handler

The current implementation updates both the model and local state. Since the model subscription will trigger a state update, the manual state update is redundant.

             onChange={(values) => {
                 message.recipients = values;
-                setRecipients(values);
             }}
admin/src/Components/MemberForm.js (1)

73-78: Reconsider number type for zipcode field

Using type="number" for zipcodes can be problematic as it:

  • Removes leading zeros
  • Prevents entering spaces or dashes
  • Shows spinner controls which are unnecessary for postal codes
-                        type="number"
+                        type="text"
+                        pattern="[0-9 -]*"
admin/src/Components/CollectionTable.js (4)

Line range hint 71-115: Improve pagination accessibility and performance

The pagination implementation has room for improvements:

  1. Missing ARIA labels for better accessibility
  2. Inline function creation in onClick could cause unnecessary re-renders
  3. Missing keyboard navigation support

Consider these improvements:

 <ul className="uk-pagination" style={{ clear: "both" }}>
     {_.range(1, page.count + 1).map((i) => {
         const distance = Math.abs(i - page.index);
         if (distance === 0) {
             return (
-                <li key={i} className="uk-active">
-                    <span>{i}</span>
+                <li key={i} className="uk-active" aria-current="page">
+                    <span aria-label={`Current Page, Page ${i}`}>{i}</span>
                 </li>
             );
         } else if (
             distance <= show_count ||
             i === 1 ||
             i === page.count
         ) {
             return (
                 <li key={i}>
                     <a
+                        role="button"
+                        tabIndex={0}
+                        aria-label={`Go to Page ${i}`}
                         onClick={() => {
                             setLoading(true);
                             if (onPageNav) onPageNav(i);
                         }}
+                        onKeyPress={(e) => {
+                            if (e.key === 'Enter' || e.key === ' ') {
+                                setLoading(true);
+                                if (onPageNav) onPageNav(i);
+                            }
+                        }}
                     >
                         {i}
                     </a>
                 </li>
             );
         }
     })}
 </ul>

117-124: Consider adding user feedback for delete operations

While the delete operation handles the basic flow, it would be beneficial to provide feedback to users about the success or failure of the operation.

Consider adding a toast notification:

 const deleteItem = (collection, item) => {
     return confirmModal(item.deleteConfirmMessage())
         .then(() => item.del())
         .then(
-            () => collection.fetch(),
+            () => {
+                collection.fetch();
+                // Assuming you have a notification system
+                props.showNotification?.({
+                    message: 'Item successfully deleted',
+                    type: 'success'
+                });
+            },
-            () => null,
+            (error) => {
+                props.showNotification?.({
+                    message: 'Failed to delete item',
+                    type: 'error'
+                });
+                return null;
+            }
         );
 };

157-167: Enhance table accessibility

The table is missing important accessibility attributes that would make it more usable for screen readers.

Consider adding these accessibility improvements:

 <table
     className={
         "uk-table uk-table-condensed uk-table-striped uk-table-hover" +
         (loading ? " backboneTableLoading" : "")
     }
+    role="grid"
+    aria-busy={loading}
+    aria-label="Collection Data Table"
 >
-    <thead>
+    <thead role="rowgroup">
-        <tr>{headers}</tr>
+        <tr role="row">{headers}</tr>
     </thead>
-    <tbody>{rows}</tbody>
+    <tbody role="rowgroup">{rows}</tbody>
 </table>

172-173: Internationalize loading message

The loading message is hardcoded in Swedish. Consider using an internationalization system for better maintainability and localization support.

-<i className="uk-icon-refresh uk-icon-spin" /> Hämtar data...
+<i className="uk-icon-refresh uk-icon-spin" /> {t('common.loading')}
admin/src/Components/FileInput.jsx (1)

40-40: Add PropTypes for better development experience

Consider adding PropTypes to document and validate the component's props.

+import PropTypes from 'prop-types';

 const FileInput = ({ onFileSelect, acceptedFileTypes, maxFileSize }) => {
   // ... component implementation
 };

+FileInput.propTypes = {
+  onFileSelect: PropTypes.func,
+  acceptedFileTypes: PropTypes.string,
+  maxFileSize: PropTypes.number
+};

 export default FileInput;
admin/src/Membership/KeyEdit.jsx (1)

1-105: Well-executed component conversion with room for enhancement

The conversion from class to function component is well done, properly utilizing React hooks and maintaining the original functionality. Consider these architectural improvements for future iterations:

  1. Add a custom hook for key management to encapsulate the subscription logic
  2. Implement proper error boundaries for error handling
  3. Consider adding loading states for async operations
admin/src/Components/DatePeriodInput.js (2)

1-5: Add PropTypes validation for the period prop.

Consider adding PropTypes to validate the shape and required properties of the period prop, which would help catch potential issues during development.

import React, { useEffect, useState } from "react";
import DayPickerInput from "react-day-picker/DayPickerInput";
import { formatUtcDate, parseUtcDate, utcToday } from "../utils";
+import PropTypes from 'prop-types';

const DatePeriodInput = ({ period }) => {

Add at the bottom:

+DatePeriodInput.propTypes = {
+  period: PropTypes.shape({
+    start: PropTypes.instanceOf(Date),
+    end: PropTypes.instanceOf(Date),
+    subscribe: PropTypes.func.isRequired,
+    isDirty: PropTypes.func.isRequired,
+    isValid: PropTypes.func.isRequired
+  }).isRequired
+};

31-74: Enhance accessibility and reduce code duplication.

  1. The DayPickerInput components share similar props and structure. Consider extracting a reusable component.
  2. Add proper ARIA labels for better accessibility.
+const DateInput = ({ value, onChange, isDirty, isValid, isHistoric }) => (
+    <span className={isDirty ? "ma-changed" : ""}>
+        <DayPickerInput
+            inputProps={{
+                size: 10,
+                className: "uk-input" + (isValid ? "" : " uk-form-danger"),
+                style: {
+                    marginTop: "2px",
+                    ...(isHistoric && historicStyle),
+                },
+                "aria-label": `${isDirty ? "Modified " : ""}date input`
+            }}
+            placeholder="YYYY-MM-DD"
+            value={value || ""}
+            parseDate={parseUtcDate}
+            formatDate={formatUtcDate}
+            onDayChange={onChange}
+        />
+    </span>
+);

 return (
     <span>
-        {/* Input for start date */}
-        <span className={period.isDirty("start") ? "ma-changed" : ""}>
-            <DayPickerInput
-                inputProps={{...}}
-                placeholder="YYYY-MM-DD"
-                value={start || ""}
-                parseDate={parseUtcDate}
-                formatDate={formatUtcDate}
-                onDayChange={(date) => handleDayChange(date, "start")}
-            />
-        </span>
+        <DateInput
+            value={start}
+            onChange={(date) => handleDayChange(date, "start")}
+            isDirty={period.isDirty("start")}
+            isValid={period.isValid("start")}
+            isHistoric={start && start < today}
+        />
         &nbsp;-&nbsp;
-        {/* Input for end date */}
-        <span className={period.isDirty("end") ? "ma-changed" : ""}>
-            <DayPickerInput {...similar props} />
-        </span>
+        <DateInput
+            value={end}
+            onChange={(date) => handleDayChange(date, "end")}
+            isDirty={period.isDirty("end")}
+            isValid={period.isValid("end")}
+            isHistoric={end && end < today}
+        />
     </span>
 );
admin/src/Components/SelectInput.jsx (1)

11-22: Consider enhancing data fetching with abort controller

While the data fetching logic is correct, it could benefit from proper cleanup and error handling.

Consider applying this improvement:

 useEffect(() => {
+    const controller = new AbortController();
     if (props.dataSource) {
         let newOptions = [];
         if (props.nullOption) {
             newOptions = [props.nullOption];
         }
-        get({ url: props.dataSource, params: { page_size: 0 } }).then(
+        get({ 
+            url: props.dataSource, 
+            params: { page_size: 0 },
+            signal: controller.signal 
+        }).then(
             (data) => setOptions(newOptions.concat(data.data)),
-            () => null,
+            (error) => {
+                if (!error.name === 'AbortError') {
+                    console.error('Failed to fetch options:', error);
+                }
+            }
         );
     }
+    return () => controller.abort();
 }, [props.dataSource, props.nullOption]);
admin/src/Components/ProductForm.jsx (2)

96-99: Simplify form submission handling

The current implementation uses both return false and e.preventDefault() which is redundant.

Choose one approach:

 onSubmit={(e) => {
-    onSave();
-    return false;
+    e.preventDefault();
+    onSave();
 }}

Or:

 onSubmit={(e) => {
     onSave();
     return false;
 }}

101-259: Consider extracting common fieldset patterns

The form has multiple fieldsets with similar structures. Consider extracting a reusable FormSection component to reduce repetition and improve maintainability.

Example implementation:

const FormSection = ({ icon, title, children }) => (
    <fieldset className="uk-margin-top">
        <legend>
            <i className={`uk-icon-${icon}`} /> {title}
        </legend>
        {children}
    </fieldset>
);

Usage:

<FormSection icon="shopping-cart" title="Produkt">
    <TextInput model={product} name="name" title="Produktnamn" />
    {/* ... other inputs */}
</FormSection>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d69424c and f82d9d8.

📒 Files selected for processing (13)
  • admin/src/Components/CollectionTable.js (5 hunks)
  • admin/src/Components/DatePeriodInput.js (1 hunks)
  • admin/src/Components/FileInput.jsx (1 hunks)
  • admin/src/Components/MemberForm.js (1 hunks)
  • admin/src/Components/MessageForm.jsx (3 hunks)
  • admin/src/Components/ProductForm.jsx (1 hunks)
  • admin/src/Components/RequestPasswordReset.jsx (1 hunks)
  • admin/src/Components/SearchBox.jsx (1 hunks)
  • admin/src/Components/SelectInput.jsx (1 hunks)
  • admin/src/Components/TextInput.jsx (1 hunks)
  • admin/src/Components/Textarea.jsx (1 hunks)
  • admin/src/Membership/GroupBoxEditInfo.jsx (1 hunks)
  • admin/src/Membership/KeyEdit.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • admin/src/Components/SearchBox.jsx
  • admin/src/Membership/GroupBoxEditInfo.jsx
🧰 Additional context used
📓 Learnings (1)
admin/src/Membership/KeyEdit.jsx (1)
Learnt from: emanuelen5
PR: makerspace/makeradmin#581
File: admin/src/Membership/KeyEdit.jsx:2-2
Timestamp: 2024-12-09T22:05:34.237Z
Learning: In React components using React Router v6, if hooks like `useParams` are used, the `withRouter` higher-order component is unnecessary and should be removed to simplify the code.
🪛 Biome (1.9.4)
admin/src/Components/Textarea.jsx

[error] 52-52: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Components/TextInput.jsx

[error] 56-57: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Components/ProductForm.jsx

[error] 35-35: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

admin/src/Components/SelectInput.jsx

[error] 52-52: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (23)
admin/src/Components/RequestPasswordReset.jsx (2)

1-7: LGTM! Clean imports and modern component declaration.

The component has been properly converted to a functional component with appropriate imports.


8-12: LGTM! Clean state management implementation.

The component has been properly converted to use controlled input with useState.

admin/src/Components/Textarea.jsx (1)

52-52: 🛠️ Refactor suggestion

Refactor the onChange handler for better maintainability

The current implementation directly assigns to the model within an expression, which can be confusing and harder to maintain.

-            onChange={(e) => (model[name] = e.target.value)}
+            onChange={(e) => {
+                model[name] = e.target.value;
+                handleModelChange();
+            }}
🧰 Tools
🪛 Biome (1.9.4)

[error] 52-52: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Components/TextInput.jsx (1)

55-58: 🛠️ Refactor suggestion

Simplify the onChange handler and add model update trigger

The current implementation has a complex nested structure and lacks a model update trigger.

-            onChange={(event) =>
-                (model[name] =
-                    event.target.value === "" ? null : event.target.value)
-            }
+            onChange={(event) => {
+                const newValue = event.target.value === "" ? null : event.target.value;
+                model[name] = newValue;
+                handleModelChange();
+            }}
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-57: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Components/MemberForm.js (3)

1-18: LGTM! Clean hooks implementation

The conversion to hooks is well-implemented with proper cleanup and dependency management.


24-27: 🛠️ Refactor suggestion

Modernize form submission handling

The current approach of returning false is an older pattern. Modern React code should explicitly prevent the default form action.

-                    onSave();
-                    return false;
+                    e.preventDefault();
+                    onSave();

Likely invalid or redundant comment.


10-13: Verify subscription behavior to prevent infinite renders

Given the reported regressions in the PR with infinite network requests, let's verify the member subscription implementation.

admin/src/Components/FileInput.jsx (2)

6-8: ⚠️ Potential issue

Implement file upload handlers

The component is missing essential functions for handling file selection and upload.

 const clearUpload = () => {
     setFilename("");
+    setUploadProgress(0);
+    setUploadStatus('idle');
+    setErrorMessage('');
 };

+const handleFileSelect = (event) => {
+    const file = event.target.files[0];
+    if (file) {
+        setFilename(file.name);
+        onFileSelect?.(file);
+    }
+};

Likely invalid or redundant comment.


1-40: Verify component usage patterns

Let's check how this component is used across the codebase to ensure our changes are compatible with existing implementations.

admin/src/Membership/KeyEdit.jsx (6)

1-9: LGTM! Imports correctly updated for functional component

The imports have been properly updated to include necessary hooks and the direct import of useParams without withRouter, aligning with React Router v6 best practices.


52-63: Consider renaming DateTimeInput to DateTimeDisplay

Based on previous feedback, DateTimeInput is actually a readonly component.

Consider creating a new issue to rename this component to DateTimeDisplay since it's readonly. This would make the component's purpose clearer.


105-105: LGTM! Clean export without HOC

The export statement has been correctly updated to remove the withRouter HOC, aligning with modern React Router practices.


24-26: 🛠️ Refactor suggestion

Add error handling to onSave

The onSave function should include error handling similar to onDelete.

 const onSave = useCallback(() => {
-    keyRef.current.save();
+    keyRef.current.save()
+        .then(() => {
+            // Consider showing a success message
+        })
+        .catch((error) => {
+            // Handle save errors
+            console.error('Failed to save key:', error);
+        });
 }, []);

15-22: Verify subscription cleanup implementation

The subscription setup and cleanup look correct. Let's verify the unsubscribe implementation in the Key model.

✅ Verification successful

Based on the search results, I can see a clear pattern of subscription cleanup implementation across the codebase. The subscribe method is defined in admin/src/Models/Base.js which returns an unsubscribe function that removes the subscriber from the subscribers list. This pattern is consistently used throughout the codebase, including in the file under review.

Let's verify the implementation in the Base model to ensure the cleanup is properly handled:


Let me try a different approach to get the Base model implementation:


Subscription cleanup implementation is correct

The subscription cleanup in the KeyEdit.jsx component is properly implemented. The Base model's subscribe method returns a cleanup function that removes the subscriber from the list, and the component correctly uses this cleanup function in the useEffect's return callback. This pattern is consistently used across the codebase and follows React's best practices for cleanup.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for subscribe/unsubscribe implementation
rg -A 5 "subscribe\(|unsubscribe\(" 

Length of output: 25249


Script:

#!/bin/bash
# Get the Base model implementation
ast-grep --pattern 'subscribe(callback) {
  $$$
}'

Length of output: 52


Script:

#!/bin/bash
# Get the Base model implementation with more context
rg -A 10 "subscribe\(callback\)" admin/src/Models/Base.js

Length of output: 452


10-13: Verify the Key instance lifecycle management

The Key instance is stored in a ref and initialized only once. Let's verify that this doesn't cause any issues with updates or re-renders.

✅ Verification successful

Let me gather more information about the Base model and any potential update mechanisms.


Let me try one more search with corrected patterns:


Based on the gathered information, I can now provide the final response:

The Key instance ref usage is implemented correctly

The implementation is safe because:

  • The Key instance is properly initialized using Key.get() which fetches fresh data via refresh()
  • The component subscribes to Key model changes in useEffect and properly cleans up the subscription
  • The Base model (parent of Key) has proper refresh and update mechanisms
  • The ref is used to maintain a stable reference to the Key instance across re-renders while allowing the instance itself to be mutable
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other components using Key.get to understand the pattern
ast-grep --pattern 'Key.get($_)'

# Search for Key model implementation
rg -A 10 "class Key"

Length of output: 2121


Script:

#!/bin/bash
# Search for Base model implementation
rg -A 10 "class Base extends|class Base {" 

# Look for any update or refresh methods in Key or Base
ast-grep --pattern 'update($$$)'

# Search for Key.get implementation
ast-grep --pattern 'get($_) {
  $$$
}'

# Look for useEffect usage with Key
rg -A 5 "useEffect.*Key"

Length of output: 397


Script:

#!/bin/bash
# Search for Base model implementation with fixed pattern
rg -A 10 "^class Base"

# Look for model update patterns in the specific files
rg -A 5 "update|refresh" admin/src/Models/

# Search for effects in KeyEdit
rg -A 5 "useEffect" admin/src/Membership/KeyEdit.jsx

# Look for Key.get static method
rg -A 5 "static get" admin/src/Models/Key.js

Length of output: 9481

admin/src/Components/DatePeriodInput.js (2)

6-16: LGTM! Clean implementation of state and effects.

The implementation correctly:

  • Initializes state with period values
  • Sets up subscription with proper cleanup
  • Uses appropriate dependency array

9-16: Verify subscription handling to prevent infinite requests.

Given the reported regressions in the PR comments about infinite network requests, we should verify that the subscription handling in this component isn't contributing to the issue.

admin/src/Components/SelectInput.jsx (3)

2-9: LGTM! Clean conversion to function component

The component setup follows React best practices with proper hooks usage and state initialization.


24-37: LGTM! Well-implemented subscription management

The subscription setup and cleanup are properly handled with appropriate dependencies.


52-52: ⚠️ Potential issue

Avoid assignment within an expression in the onChange handler

The assignment within the expression can lead to confusion and side effects.

Apply this improvement:

-onChange={(o) => (props.model[props.name] = props.getValue(o))}
+onChange={(o) => {
+    props.model[props.name] = props.getValue(o);
+}}
🧰 Tools
🪛 Biome (1.9.4)

[error] 52-52: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

admin/src/Components/ProductForm.jsx (3)

12-22: LGTM! Well-structured utility functions

The utility functions are well-implemented with:

  • Modern array methods replacing lodash dependencies
  • Proper null checks and fallback logic
  • Clear single responsibilities

53-63: LGTM! Well-implemented event handlers

The handlers are concise, focused, and include proper checks before mutations.


34-37: ⚠️ Potential issue

Fix variable usage before declaration

The variable selectedActionType is being used before it's declared.

Apply this fix:

-            const selectedActionType = filterSelectedActionType(
+            const newSelectedActionType = filterSelectedActionType(
                 selectedActionType,
                 newAvailableActionTypes,
             );
🧰 Tools
🪛 Biome (1.9.4)

[error] 35-35: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

model={message}
name="subject"
title="Ärende"
onChange={() => setBodyLength(message.body.length)} // Ensure the length is updated when body changes
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consolidate duplicate onChange handlers

The same onChange handler is duplicated for both TextInput and Textarea components.

+    const handleBodyChange = useCallback(() => {
+        setBodyLength(message.body.length);
+    }, [message.body]);

     <TextInput
         model={message}
         name="subject"
         title="Ärende"
-        onChange={() => setBodyLength(message.body.length)}
+        onChange={handleBodyChange}
     />
     <Textarea
         model={message}
         name="body"
         title="Meddelande"
-        onChange={() => setBodyLength(message.body.length)}
+        onChange={handleBodyChange}
     />

Also applies to: 117-117

Comment on lines +130 to +135
<a
className="uk-button uk-button-danger uk-float-left"
onClick={onDelete}
>
<i className="uk-icon-save" />{" "}
{member.id ? "Spara" : "Skapa"}
</button>
</div>
</form>
</div>
);
}
}
<i className="uk-icon-trash" /> Ta bort medlem
</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve delete button accessibility and semantics

The delete action uses an anchor tag (<a>) with onClick, which:

  • Isn't semantic HTML (anchors are for navigation)
  • May cause accessibility issues
  • Doesn't prevent default action
-                        <a
-                            className="uk-button uk-button-danger uk-float-left"
-                            onClick={onDelete}
+                        <button
+                            type="button"
+                            className="uk-button uk-button-danger uk-float-left"
+                            onClick={(e) => {
+                                e.preventDefault();
+                                onDelete();
+                            }}
-                        </a>
+                        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
className="uk-button uk-button-danger uk-float-left"
onClick={onDelete}
>
<i className="uk-icon-save" />{" "}
{member.id ? "Spara" : "Skapa"}
</button>
</div>
</form>
</div>
);
}
}
<i className="uk-icon-trash" /> Ta bort medlem
</a>
<button
type="button"
className="uk-button uk-button-danger uk-float-left"
onClick={(e) => {
e.preventDefault();
onDelete();
}}
>
<i className="uk-icon-trash" /> Ta bort medlem
</button>

Comment on lines +20 to +29
useEffect(() => {
const unsubscribe = collection.subscribe(({ page, items }) => {
setPage(page);
setItems(items);
setLoading(false);
});
return () => {
unsubscribe();
};
}, [collection]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add cleanup and optimize the collection subscription effect

The current implementation has a few potential improvements:

  1. Missing cleanup of state when collection changes
  2. Hook dependencies could be optimized

Consider this implementation:

 useEffect(() => {
+    // Reset state when collection changes
+    setPage({});
+    setItems(null);
+    setLoading(true);
+    
     const unsubscribe = collection.subscribe(({ page, items }) => {
         setPage(page);
         setItems(items);
         setLoading(false);
     });
     return () => {
         unsubscribe();
     };
 }, [collection]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const unsubscribe = collection.subscribe(({ page, items }) => {
setPage(page);
setItems(items);
setLoading(false);
});
return () => {
unsubscribe();
};
}, [collection]);
useEffect(() => {
// Reset state when collection changes
setPage({});
setItems(null);
setLoading(true);
const unsubscribe = collection.subscribe(({ page, items }) => {
setPage(page);
setItems(items);
setLoading(false);
});
return () => {
unsubscribe();
};
}, [collection]);

Comment on lines +128 to +135
rows = items.map((item, i) => (
<React.Fragment key={i}>
{rowComponent({
item,
deleteItem: () => deleteItem(collection, item),
})}
</React.Fragment>
));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using array indices as React keys

Using array indices as keys can lead to performance issues and bugs with item reordering or deletion.

Consider using a unique identifier from the item:

-rows = items.map((item, i) => (
-    <React.Fragment key={i}>
+rows = items.map((item) => (
+    <React.Fragment key={item.id || item.uniqueIdentifier}>

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

import React, { useState } from "react";

const FileInput = () => {
const [filename, setFilename] = useState("");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing state for upload handling

The component needs additional state variables to manage the upload process and provide feedback to users.

 const [filename, setFilename] = useState("");
+const [uploadProgress, setUploadProgress] = useState(0);
+const [uploadStatus, setUploadStatus] = useState('idle'); // idle, uploading, success, error
+const [errorMessage, setErrorMessage] = useState('');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [filename, setFilename] = useState("");
const [filename, setFilename] = useState("");
const [uploadProgress, setUploadProgress] = useState(0);
const [uploadStatus, setUploadStatus] = useState('idle'); // idle, uploading, success, error
const [errorMessage, setErrorMessage] = useState('');

Comment on lines +25 to +30
<input
id="upload-select"
className="uk-hidden"
type="file"
/>
</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Connect file input and add upload feedback UI

The file input needs to be connected to handlers and the UI should show upload progress and errors.

 <input
     id="upload-select"
     className="uk-hidden"
     type="file"
+    onChange={handleFileSelect}
+    accept={acceptedFileTypes}
 />
+{uploadStatus === 'uploading' && (
+    <div className="uk-margin-small-top">
+        <progress className="uk-progress" value={uploadProgress} max="100" />
+    </div>
+)}
+{uploadStatus === 'error' && (
+    <div className="uk-alert uk-alert-danger">{errorMessage}</div>
+)}

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

Comment on lines +1 to +3
import React, { useState } from "react";

const FileInput = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing props for component configuration

The component should accept props for configuring the upload behavior and handling file selection callbacks. Consider adding props like onFileSelect, acceptedFileTypes, etc.

-const FileInput = () => {
+const FileInput = ({ onFileSelect, acceptedFileTypes, maxFileSize }) => {

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

Comment on lines +18 to +26
const handleDayChange = (date, type) => {
if (type === "start") {
period.start = date || null;
setStart(date || null);
} else if (type === "end") {
period.end = date || null;
setEnd(date || null);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding date validation and avoiding direct mutations.

  1. The direct mutation of the period object (period.start = date) could lead to unexpected behavior. Consider using a proper update method if provided by the period object.

  2. There's no validation to ensure the end date isn't before the start date.

 const handleDayChange = (date, type) => {
+    // Validate date range
+    if (type === "end" && date && start && date < start) {
+        return; // or show error message
+    }
+    if (type === "start" && date && end && date > end) {
+        return; // or show error message
+    }
+
     if (type === "start") {
-        period.start = date || null;
+        // Consider using period.updateStart(date) if available
+        period.start = date || null;
         setStart(date || null);
     } else if (type === "end") {
+        // Consider using period.updateEnd(date) if available
         period.end = date || null;
         setEnd(date || null);
     }
 };

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

Comment on lines +11 to +22
useEffect(() => {
if (props.dataSource) {
let options = [];
let newOptions = [];
if (props.nullOption) {
options = [props.nullOption];
newOptions = [props.nullOption];
}
get({ url: props.dataSource, params: { page_size: 0 } }).then(
(data) => this.setState({ options: options.concat(data.data) }),
(data) => setOptions(newOptions.concat(data.data)),
() => null,
);
}
}
}, [props.dataSource, props.nullOption]);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Network request in SelectInput lacks cleanup, unlike other components

The search results show that while most components in the codebase properly implement cleanup for subscriptions, the SelectInput component's useEffect with the network request doesn't handle cleanup for potential in-flight requests. This could lead to memory leaks or race conditions if the component unmounts while the request is pending.

  • admin/src/Components/SelectInput.jsx: Add cleanup to the network request useEffect:
useEffect(() => {
    if (props.dataSource) {
        let aborted = false;
        let newOptions = [];
        if (props.nullOption) {
            newOptions = [props.nullOption];
        }
        get({ url: props.dataSource, params: { page_size: 0 } }).then(
            (data) => !aborted && setOptions(newOptions.concat(data.data)),
            () => null,
        );
        return () => { aborted = true; };
    }
}, [props.dataSource, props.nullOption]);
🔗 Analysis chain

Verify cleanup of subscriptions and network requests

Given the reported issues with infinite network requests in other components, let's verify the cleanup implementation across similar components.

Also applies to: 24-37


Let me try a different search pattern to find potential cleanup issues in useEffect hooks.

The previous pattern might have been too specific. This new search will help us:

  1. Find all useEffect hooks that make network requests
  2. Find any cleanup functions implemented in useEffect hooks
    This will give us a better picture of the cleanup implementation across components.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for components with potential cleanup issues
echo "Searching for components with useEffect and network requests..."
ast-grep --pattern 'useEffect(() => {
  $$$
  get({
    $$$
  })
  $$$
})' admin/src/Components/

echo "Searching for components with useEffect and subscriptions..."
ast-grep --pattern 'useEffect(() => {
  $$$
  subscribe($$$)
  $$$
})' admin/src/Components/

Length of output: 461


Script:

#!/bin/bash
# Search for all useEffect hooks in Components directory
echo "Searching for useEffect hooks with network requests..."
rg -U "useEffect\(.+\{[\s\S]+?get\(.+?\)[\s\S]+?\}.*\)" admin/src/Components/ -A 5

echo "Searching for cleanup functions in useEffect..."
rg -U "useEffect\(.+\{[\s\S]+?return\s+\(*\s*\(*\)\s*=>\s*\{[\s\S]+?\}" admin/src/Components/

Length of output: 12610

Comment on lines +30 to +51
useEffect(() => {
const handleProductChange = () => {
const newActions = product.actions;
const newAvailableActionTypes = filterAvailableActions(newActions);
const selectedActionType = filterSelectedActionType(
selectedActionType,
newAvailableActionTypes,
);

setActions(newActions);
setAvailableActionTypes(newAvailableActionTypes);
setSelectedActionType(selectedActionType);
setSaveDisabled(!product.canSave());
};
}

componentDidMount() {
const { product } = this.props;
this.unsubscribe = product.subscribe(() =>
this.setState(productChanged),
);
}
const unsubscribe = product.subscribe(handleProductChange);
handleProductChange();

componentWillUnmount() {
this.unsubscribe();
}
return () => {
unsubscribe();
};
}, [product, selectedActionType]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential infinite loop in useEffect

The useEffect hook includes selectedActionType in its dependency array while also updating it inside the effect. This can cause an infinite loop of updates.

Apply this fix to remove the unnecessary dependency:

    useEffect(() => {
        const handleProductChange = () => {
            const newActions = product.actions;
            const newAvailableActionTypes = filterAvailableActions(newActions);
            const newSelectedActionType = filterSelectedActionType(
                selectedActionType,
                newAvailableActionTypes,
            );

            setActions(newActions);
            setAvailableActionTypes(newAvailableActionTypes);
            setSelectedActionType(newSelectedActionType);
            setSaveDisabled(!product.canSave());
        };

        const unsubscribe = product.subscribe(handleProductChange);
        handleProductChange();

        return () => {
            unsubscribe();
        };
-    }, [product, selectedActionType]);
+    }, [product]); // selectedActionType is not needed in dependencies
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const handleProductChange = () => {
const newActions = product.actions;
const newAvailableActionTypes = filterAvailableActions(newActions);
const selectedActionType = filterSelectedActionType(
selectedActionType,
newAvailableActionTypes,
);
setActions(newActions);
setAvailableActionTypes(newAvailableActionTypes);
setSelectedActionType(selectedActionType);
setSaveDisabled(!product.canSave());
};
}
componentDidMount() {
const { product } = this.props;
this.unsubscribe = product.subscribe(() =>
this.setState(productChanged),
);
}
const unsubscribe = product.subscribe(handleProductChange);
handleProductChange();
componentWillUnmount() {
this.unsubscribe();
}
return () => {
unsubscribe();
};
}, [product, selectedActionType]);
useEffect(() => {
const handleProductChange = () => {
const newActions = product.actions;
const newAvailableActionTypes = filterAvailableActions(newActions);
const selectedActionType = filterSelectedActionType(
selectedActionType,
newAvailableActionTypes,
);
setActions(newActions);
setAvailableActionTypes(newAvailableActionTypes);
setSelectedActionType(selectedActionType);
setSaveDisabled(!product.canSave());
};
const unsubscribe = product.subscribe(handleProductChange);
handleProductChange();
return () => {
unsubscribe();
};
}, [product]); // selectedActionType is not needed in dependencies
🧰 Tools
🪛 Biome (1.9.4)

[error] 35-35: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

Copy link
Member

@emanuelen5 emanuelen5 left a comment

Choose a reason for hiding this comment

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

There are a few things we should clean up, but we can do all of that in follow-up PRs. This seems to work, and is quite the improvement. Thanks a lot! ❤️

);
};

export default withRouter(KeyHandout);
Copy link
Member

Choose a reason for hiding this comment

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

Remove withRouter

children: PropTypes.node,
};

export default withRouter(MemberBox);
Copy link
Member

Choose a reason for hiding this comment

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

Remove withRouter

Comment on lines +79 to 81
MemberBox.propTypes = {
children: PropTypes.node,
};
Copy link
Member

Choose a reason for hiding this comment

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

Remove propTypes

Comment on lines +87 to +88
e.preventDefault();
createKey();
Copy link
Member

Choose a reason for hiding this comment

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

Same here, instead remove e.preventDefault() and keep return false.

Comment on lines +16 to +23
const collectionRef = useRef(
new Collection({
type: Group,
url: `/membership/member/${props.match.params.member_id}/groups`,
idListName: "groups",
pageSize: 0,
});
this.state = {
items: [],
options: [],
showOptions: [],
selectedOption: null,
};
}),
);
Copy link
Member

Choose a reason for hiding this comment

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

useRef won't work here if you re-render with an updated member_id - then the collection will still use the old member's data. Instead, I think the easiest is to use useMemo with the member_id as dependency.

Comment on lines +20 to +27
const collectionRef = useRef(
new Collection({
type: Span,
url: `/membership/member/${props.match.params.member_id}/spans`,
url: `/membership/member/${memberId}/spans`,
pageSize: 0,
includeDeleted: true,
});
this.state = { items: [], pending_labaccess_days: "?" };
this.pending_actions = get({
url: `/membership/member/${props.match.params.member_id}/pending_actions`,
}).then((r) => {
const sum_pending_labaccess_days = r.data.reduce((acc, value) => {
if (value.action.action === ADD_LABACCESS_DAYS)
return acc + value.action.value;
return acc;
}, 0);
this.setState({
pending_labaccess_days: sum_pending_labaccess_days,
});
});
}
}),
);
Copy link
Member

Choose a reason for hiding this comment

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

Same here:

useRef causes the collection to never update, even if the member_id is changed. That is a bug. To fix that, you can either create the collection on each render:

const collection = new Collection(...);

or memoize it with correct dependencies, to just create it when needed:

const collection = useMemo(() => new Collection(...), [member_id]);

useEffect(() => {
const unsubscribes = [];

// Subscribe to spans updates
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

componentWillUnmount() {
this.unsubscribe.forEach((u) => u());
}
// Subscribe to categoryPeriods updates
Copy link
Member

Choose a reason for hiding this comment

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

Remove comment

Comment on lines +83 to +93
// post({
// url: `/webshop/member/${member_id}/ship_labaccess_orders`,
// expectedDataStatus: "ok",
// });
post({
url: `/webshop/member/${member_id}/ship_labaccess_orders`,
headers: { "Content-Type": "application/json" },
payload: {
/* any required data */
},
expectedDataStatus: "ok",
Copy link
Member

Choose a reason for hiding this comment

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

Revert. Or was there a reason it had to change?

function MemberBoxSpans(props) {
const memberId = props.match.params.member_id;

const [, setItems] = useState([]);
Copy link
Member

Choose a reason for hiding this comment

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

Remove the unused state

@emanuelen5 emanuelen5 merged commit 603c7c5 into makerspace:main Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

legacy Old stuff that might need updating or refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert React components to functions

4 participants