Skip to content

fix: add popup to manual scan badge on sponsor badge scans tab#835

Open
tomrndom wants to merge 2 commits intomasterfrom
fix/sponsor-badge-scan-manual
Open

fix: add popup to manual scan badge on sponsor badge scans tab#835
tomrndom wants to merge 2 commits intomasterfrom
fix/sponsor-badge-scan-manual

Conversation

@tomrndom
Copy link

@tomrndom tomrndom commented Mar 20, 2026

ref: https://app.clickup.com/t/86b7tdv03

Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com

Summary by CodeRabbit

  • New Features
    • QR scanning popup for badge scans with manual submit, admin-only metadata, notes, and extra-questions support; manual scan submission triggers save and list refresh.
  • Bug Fixes
    • Loading indicators now stop reliably after badge-scan requests (success or failure).
    • Extra-questions checkbox answers parsed safely to avoid invalid values.
  • Localization
    • Added scan-popup UI strings for QR scanning flow.
  • Chores
    • Example environment scopes updated to include badge-scans write permission.

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Adds sponsor badge-scan functionality: new MUI QR scan dialog, Redux action/thunk to create scans, page wiring to open/save scans, i18n strings, utility tweak for checkbox parsing, and an added badge-scans write scope in .env.example.

Changes

Cohort / File(s) Summary
Env / Auth
\.env.example
Added ${SCOPES_BASE_REALM}/summits/badge-scans/write to the SCOPES list (ordering adjusted for badge-scans scopes).
Redux actions
src/actions/sponsor-actions.js
Added BADGE_SCAN_ADDED constant and addBadgeScan(entity) thunk; changed multiple thunks to call stopLoading() in .finally(...); expanded getSponsor expand to include extra_questions.values; adjusted getBadgeScan/saveBadgeScan to stop loading in .finally(...).
UI component
src/components/mui/mui-qr-badge-popup.js
New default-exported MuiQrBadgePopup component: QR reader with scan handling, rescan flow, Formik form for admin extra questions and notes, transforms form values to extra_questions, calls onSave with { qr_code, scan_date, notes, extra_questions }.
Page integration
src/pages/sponsors/sponsor-badge-scans/index.js
Connected addBadgeScan and member props; added showManualBadgeScanPopup state, handler to open popup, handleManualScanSubmit to dispatch addBadgeScan and refresh list, and conditional rendering of MuiQrBadgePopup.
i18n
src/i18n/en.json
Added sponsor_badge_scans.scan_popup keys: scan_qr, error, badge_scanned, rescan.
Utils
src/utils/extra-questions.js
Adjusted getTypeValue QuestionType_CheckBoxList handling to return ans.split(",") only when ans is truthy, otherwise [] (removed optional chaining usage).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as SponsorBadgeScans<br/>Page
    participant Popup as MuiQrBadgePopup<br/>Component
    participant Redux as Redux Store<br/>(sponsor-actions)
    participant API as Badge Scans API<br/>/badge-scans

    User->>Page: Click "New Manual Scan"
    Page->>Page: set showManualBadgeScanPopup = true
    Page->>Popup: render with extraQuestions & isAdmin

    User->>Popup: Scan QR code
    Popup->>Popup: set scannedCode, show success state
    User->>Popup: Fill form, click Save
    Popup->>Redux: dispatch addBadgeScan(entity)
    Redux->>Redux: startLoading()
    Redux->>API: POST /badge-scans (entity)
    API-->>Redux: 200 OK
    Redux->>Redux: snackbarSuccessHandler, stopLoading()
    Redux-->>Page: resolve addBadgeScan
    Page->>Page: getBadgeScans(sponsor.id), close popup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • caseylocker
  • smarcet

Poem

🐰 I hopped in to scan the flashing square,

QR blinked bright and I caught it there,
Formik carrots, Redux leaves to munch,
Notes and extras tucked in for lunch,
Hop, scan, submit — a crunchy little crunch.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a popup for manual badge scanning on the sponsor badge scans tab, which aligns with the substantial changes across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sponsor-badge-scan-manual

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

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

🧹 Nitpick comments (1)
src/components/mui/mui-qr-badge-popup.js (1)

151-154: Avoid mutating extraQuestions during render.

sort(...) mutates the same array instance received from the parent, which is risky when that data comes from Redux-backed props. Clone it before sorting.

💡 Suggested fix
                 <ExtraQuestionsMUI
-                  extraQuestions={extraQuestions.sort(
+                  extraQuestions={[...extraQuestions].sort(
                     (a, b) => a.order - b.order
                   )}
                   formik={formik}
                   allowEdit
                 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mui/mui-qr-badge-popup.js` around lines 151 - 154, The render
currently calls extraQuestions.sort(...) which mutates the prop array; clone
extraQuestions before sorting to avoid mutating parent/Redux state (e.g., use
extraQuestions.slice().sort(...) or [...extraQuestions].sort(...)) and pass that
cloned, sorted array into ExtraQuestionsMUI; also guard for undefined/null
extraQuestions before cloning if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/actions/sponsor-actions.js`:
- Around line 1476-1502: In addBadgeScan, read the active sponsor from the Redux
state (e.g., from currentSponsorState.activeSponsor or selectedSponsor returned
by getState()) and inject it into the payload before calling postRequest (for
example add sponsor_id: activeSponsor.id or sponsor: activeSponsor as required
by the API), then pass the new payload (not the original entity) into
postRequest; also handle the case where no active sponsor is present (no-op or
early return/error) to avoid sending an un-scoped summit-level badge scan.

In `@src/components/mui/mui-qr-badge-popup.js`:
- Around line 50-70: The onSubmit handler in useFormik currently calls
onScan(...) without preventing duplicate submits; wrap the create call in a
submitting guard by setting formik.setSubmitting(true) at the start of onSubmit,
await the onScan(...) call (assume/ensure onScan returns a Promise), and then
call formik.setSubmitting(false) in a finally block; also ensure the Save button
is disabled using formik.isSubmitting so the UI cannot be clicked while the
create is in flight. Target the useFormik onSubmit function and the Save button
binding to formik.isSubmitting (and adjust onScan to return a Promise if it
doesn’t already).

In `@src/pages/sponsors/sponsor-badge-scans/index.js`:
- Around line 111-114: handleManualScanSubmit currently calls
addBadgeScan(...).then(...).finally(...) which always closes the MuiQrBadgePopup
even on failure; change it to return the promise chain and only close the popup
in the success path: call addBadgeScan(entity).then(() =>
getBadgeScans(sponsor.id)).then(() => setShowManualBadgeScanPopup(false)) and
return that promise so callers can await/rely on errors (do not use .finally to
close). Ensure any errors are allowed to propagate so the popup can keep form
state for retry.

---

Nitpick comments:
In `@src/components/mui/mui-qr-badge-popup.js`:
- Around line 151-154: The render currently calls extraQuestions.sort(...) which
mutates the prop array; clone extraQuestions before sorting to avoid mutating
parent/Redux state (e.g., use extraQuestions.slice().sort(...) or
[...extraQuestions].sort(...)) and pass that cloned, sorted array into
ExtraQuestionsMUI; also guard for undefined/null extraQuestions before cloning
if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65b591a0-e4eb-49f5-b47e-0f3f27748335

📥 Commits

Reviewing files that changed from the base of the PR and between 2159651 and 83f8857.

📒 Files selected for processing (5)
  • .env.example
  • src/actions/sponsor-actions.js
  • src/components/mui/mui-qr-badge-popup.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-badge-scans/index.js

Comment on lines +1476 to +1502
export const addBadgeScan = (entity) => async (dispatch, getState) => {
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;

dispatch(startLoading());

const params = {
access_token: accessToken
};

return postRequest(
null,
createAction(BADGE_SCAN_ADDED),
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`,
entity,
snackbarErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("edit_badge_scan.badge_scan_saved")
})
);
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inject the active sponsor before posting a new badge scan.

src/components/mui/mui-qr-badge-popup.js (Lines 64-68) submits only qr_code, scan_date, notes, and extra_questions, and this thunk forwards that payload unchanged to the summit-level /badge-scans endpoint. Because the sponsor is not part of the URL, admin-created scans are not explicitly tied to the sponsor whose badge-scans tab is open.

💡 Suggested fix
 export const addBadgeScan = (entity) => async (dispatch, getState) => {
-  const { currentSummitState } = getState();
+  const { currentSummitState, currentSponsorState } = getState();
   const accessToken = await getAccessTokenSafely();
   const { currentSummit } = currentSummitState;
+  const sponsorId = currentSponsorState?.entity?.id;
 
   dispatch(startLoading());
 
   const params = {
     access_token: accessToken
   };
 
   return postRequest(
     null,
     createAction(BADGE_SCAN_ADDED),
     `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`,
-    entity,
+    { ...entity, sponsor_id: sponsorId },
     snackbarErrorHandler
   )(params)(dispatch).then(() => {
     dispatch(stopLoading());
     dispatch(
       snackbarSuccessHandler({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-actions.js` around lines 1476 - 1502, In addBadgeScan,
read the active sponsor from the Redux state (e.g., from
currentSponsorState.activeSponsor or selectedSponsor returned by getState()) and
inject it into the payload before calling postRequest (for example add
sponsor_id: activeSponsor.id or sponsor: activeSponsor as required by the API),
then pass the new payload (not the original entity) into postRequest; also
handle the case where no active sponsor is present (no-op or early return/error)
to avoid sending an un-scoped summit-level badge scan.

Comment on lines +111 to +114
const handleManualScanSubmit = (entity) => {
addBadgeScan(entity)
.then(() => getBadgeScans(sponsor.id))
.finally(() => setShowManualBadgeScanPopup(false));
Copy link

@coderabbitai coderabbitai bot Mar 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Only close the popup after a successful create, and return the request promise.

If the POST fails, this .finally(...) still unmounts MuiQrBadgePopup and drops the scanned QR plus any notes/extra-question answers. Returning the promise and closing only on success preserves the form for retry and lets the popup manage its submitting state.

💡 Suggested fix
 const handleManualScanSubmit = (entity) => {
-  addBadgeScan(entity)
-    .then(() => getBadgeScans(sponsor.id))
-    .finally(() => setShowManualBadgeScanPopup(false));
+  return addBadgeScan(entity).then(() => {
+    setShowManualBadgeScanPopup(false);
+    return getBadgeScans(
+      sponsor.id,
+      term,
+      currentPage,
+      perPage,
+      order,
+      orderDir
+    );
+  });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/sponsors/sponsor-badge-scans/index.js` around lines 111 - 114,
handleManualScanSubmit currently calls addBadgeScan(...).then(...).finally(...)
which always closes the MuiQrBadgePopup even on failure; change it to return the
promise chain and only close the popup in the success path: call
addBadgeScan(entity).then(() => getBadgeScans(sponsor.id)).then(() =>
setShowManualBadgeScanPopup(false)) and return that promise so callers can
await/rely on errors (do not use .finally to close). Ensure any errors are
allowed to propagate so the popup can keep form state for retry.

Copy link

Choose a reason for hiding this comment

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

@tomrndom please review

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@fntechgit fntechgit deleted a comment from coderabbitai bot Mar 26, 2026
}))
.filter((q) => q.answer);

onScan({
Copy link

Choose a reason for hiding this comment

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

@tomrndom please review this could lead to a doble click ( button is not disabled ) we need to prevent double submit

entity,
snackbarErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
Copy link

Choose a reason for hiding this comment

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

@tomrndom move dispatch(stopLoading()); to finally

Copy link

@smarcet smarcet left a comment

Choose a reason for hiding this comment

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

@tomrndom please review

@tomrndom tomrndom force-pushed the fix/sponsor-badge-scan-manual branch from 5c39eb7 to ca1efc3 Compare March 26, 2026 15:57
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
…checkboxlist answers

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom tomrndom force-pushed the fix/sponsor-badge-scan-manual branch from ca1efc3 to 0d06380 Compare March 26, 2026 15:59
Copy link

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

♻️ Duplicate comments (1)
src/actions/sponsor-actions.js (1)

1483-1509: ⚠️ Potential issue | 🟠 Major

Inject the active sponsor before posting the new badge scan.

This still sends the raw entity to the summit-level /badge-scans endpoint, so the created scan is not explicitly tied to the sponsor whose tab is open. In src/pages/sponsors/sponsor-badge-scans/index.js, Lines 111-115 immediately refresh the list with getBadgeScans(sponsor.id), so an unscoped create can disappear from the current sponsor view right after submit.

💡 Suggested fix
 export const addBadgeScan = (entity) => async (dispatch, getState) => {
-  const { currentSummitState } = getState();
+  const { currentSummitState, currentSponsorState } = getState();
   const accessToken = await getAccessTokenSafely();
   const { currentSummit } = currentSummitState;
+  const sponsorId = currentSponsorState?.entity?.id;
+
+  if (!sponsorId) {
+    return Promise.reject(new Error("Missing sponsor context"));
+  }
 
   dispatch(startLoading());
 
   const params = {
     access_token: accessToken
   };
+
+  const normalizedEntity = normalizeBadgeScan({
+    ...entity,
+    sponsor_id: sponsorId
+  });
 
   return postRequest(
     null,
     createAction(BADGE_SCAN_ADDED),
     `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`,
-    entity,
+    normalizedEntity,
     snackbarErrorHandler
   )(params)(dispatch)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-actions.js` around lines 1483 - 1509, In addBadgeScan,
ensure the posted payload includes the active sponsor so the created scan is
scoped to the open sponsor tab: read the active sponsor from state (e.g.,
currentSponsorState.activeSponsor or selectedSponsor), clone the incoming entity
and inject the sponsor identifier (e.g., set sponsor_id = sponsor.id or sponsor
= { id: sponsor.id } per API) before calling postRequest to
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`; do this
prior to invoking postRequest/createAction(BADGE_SCAN_ADDED) and avoid mutating
the original entity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/actions/sponsor-actions.js`:
- Around line 1483-1509: In addBadgeScan, ensure the posted payload includes the
active sponsor so the created scan is scoped to the open sponsor tab: read the
active sponsor from state (e.g., currentSponsorState.activeSponsor or
selectedSponsor), clone the incoming entity and inject the sponsor identifier
(e.g., set sponsor_id = sponsor.id or sponsor = { id: sponsor.id } per API)
before calling postRequest to
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`; do this
prior to invoking postRequest/createAction(BADGE_SCAN_ADDED) and avoid mutating
the original entity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9d3fa66-0d47-4666-b7d8-787cd50b0bb7

📥 Commits

Reviewing files that changed from the base of the PR and between 83f8857 and 5c39eb7.

📒 Files selected for processing (4)
  • src/actions/sponsor-actions.js
  • src/components/mui/mui-qr-badge-popup.js
  • src/pages/sponsors/sponsor-badge-scans/index.js
  • src/utils/extra-questions.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/sponsors/sponsor-badge-scans/index.js
  • src/components/mui/mui-qr-badge-popup.js

Copy link

@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

♻️ Duplicate comments (1)
src/actions/sponsor-actions.js (1)

1483-1510: ⚠️ Potential issue | 🟠 Major

Inject sponsor ID into the badge scan payload.

The addBadgeScan thunk posts to the summit-level /badge-scans endpoint without including a sponsor_id. This means scans created via the manual QR popup won't be associated with the sponsor whose badge-scans tab is open.

🔧 Suggested fix
 export const addBadgeScan = (entity) => async (dispatch, getState) => {
-  const { currentSummitState } = getState();
+  const { currentSummitState, currentSponsorState } = getState();
   const accessToken = await getAccessTokenSafely();
   const { currentSummit } = currentSummitState;
+  const sponsorId = currentSponsorState?.entity?.id;
+
+  if (!sponsorId) {
+    dispatch(stopLoading());
+    return;
+  }

   dispatch(startLoading());

   const params = {
     access_token: accessToken
   };

   return postRequest(
     null,
     createAction(BADGE_SCAN_ADDED),
     `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`,
-    entity,
+    { ...entity, sponsor_id: sponsorId },
     snackbarErrorHandler
   )(params)(dispatch)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-actions.js` around lines 1483 - 1510, The addBadgeScan
thunk posts the provided entity to the summit-level badge-scans endpoint but
never attaches the sponsor identifier, so scans won't be tied to the sponsor
currently open; modify addBadgeScan to read the current sponsor from getState
(e.g. const { currentSponsor } = getState().sponsorState) and inject sponsor_id
into the payload (either by mutating or building a new payload like {...entity,
sponsor_id: currentSponsor.id}) before calling postRequest (the call that
creates BADGE_SCAN_ADDED), then continue to dispatch success/finally as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/mui/mui-qr-badge-popup.js`:
- Around line 152-154: The code is mutating the extraQuestions prop by calling
extraQuestions.sort(...); instead, create a shallow copy before sorting (e.g.
[...extraQuestions].sort(...) or extraQuestions.slice().sort(...)) where the
prop is passed (the JSX prop assignment for extraQuestions) so the original prop
is not mutated and parent state is preserved.

---

Duplicate comments:
In `@src/actions/sponsor-actions.js`:
- Around line 1483-1510: The addBadgeScan thunk posts the provided entity to the
summit-level badge-scans endpoint but never attaches the sponsor identifier, so
scans won't be tied to the sponsor currently open; modify addBadgeScan to read
the current sponsor from getState (e.g. const { currentSponsor } =
getState().sponsorState) and inject sponsor_id into the payload (either by
mutating or building a new payload like {...entity, sponsor_id:
currentSponsor.id}) before calling postRequest (the call that creates
BADGE_SCAN_ADDED), then continue to dispatch success/finally as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0911b59e-ec9b-4ef5-980d-cc83a03c7ca9

📥 Commits

Reviewing files that changed from the base of the PR and between 5c39eb7 and 0d06380.

📒 Files selected for processing (6)
  • .env.example
  • src/actions/sponsor-actions.js
  • src/components/mui/mui-qr-badge-popup.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-badge-scans/index.js
  • src/utils/extra-questions.js
✅ Files skipped from review due to trivial changes (2)
  • src/i18n/en.json
  • src/utils/extra-questions.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • .env.example
  • src/pages/sponsors/sponsor-badge-scans/index.js

Comment on lines +152 to +154
extraQuestions={extraQuestions.sort(
(a, b) => a.order - b.order
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid mutating the extraQuestions prop with .sort().

Array.prototype.sort() mutates the array in place. Since extraQuestions is a prop, mutating it can cause unexpected behavior in parent components or trigger unnecessary re-renders.

🔧 Suggested fix
                 <ExtraQuestionsMUI
-                  extraQuestions={extraQuestions.sort(
+                  extraQuestions={[...extraQuestions].sort(
                     (a, b) => a.order - b.order
                   )}
📝 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
extraQuestions={extraQuestions.sort(
(a, b) => a.order - b.order
)}
extraQuestions={[...extraQuestions].sort(
(a, b) => a.order - b.order
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mui/mui-qr-badge-popup.js` around lines 152 - 154, The code is
mutating the extraQuestions prop by calling extraQuestions.sort(...); instead,
create a shallow copy before sorting (e.g. [...extraQuestions].sort(...) or
extraQuestions.slice().sort(...)) where the prop is passed (the JSX prop
assignment for extraQuestions) so the original prop is not mutated and parent
state is preserved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants