Skip to content

Feat: Settings page#88

Open
TatevikGr wants to merge 10 commits into
devfrom
settings
Open

Feat: Settings page#88
TatevikGr wants to merge 10 commits into
devfrom
settings

Conversation

@TatevikGr

@TatevikGr TatevikGr commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added a new Settings page (/settings) with tabbed views for Configs, Admins, Admin Attributes, and Subscriber Attributes.
    • Configs: search, refresh, and per-key Save/Reset with inline feedback.
    • Admins & Attributes: responsive lists with add/edit dialogs and delete confirmation.
    • Dashboard now shows an amber error alert when dashboard data can’t be loaded.
  • Bug Fixes
    • Campaign statistics now hide when not authorized.
    • Improved tab button sizing in the bounces UI.
  • Chores
    • Updated the REST API client dependency.

Summary

Provide a general description of the code changes in your pull request …
were there any bugs you had fixed? If so, mention them. If these bugs have open
GitHub issues, be sure to tag them here as well, to keep the conversation
linked together.

Unit test

Are your changes covered with unit tests, and do they not break anything?

You can run the existing unit tests using this command:

vendor/bin/phpunit tests/

Code style

Have you checked that you code is well-documented and follows the PSR-2 coding
style?

You can check for this using this command:

vendor/bin/phpcs --standard=PSR2 src/ tests/

Other Information

If there's anything else that's important and relevant to your pull
request, mention that information here. This could include benchmarks,
or other information.

If you are updating any of the CHANGELOG files or are asked to update the
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.

Thanks for contributing to phpList!

@TatevikGr TatevikGr changed the base branch from main to dev June 17, 2026 11:36
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new /settings page with Symfony and Vue routing, a tabbed settings view, config editing, administrator CRUD, subscriber/admin attribute CRUD, and shared REST client wiring. The dashboard now passes an error message into the SPA and shows it in the Vue view when stats loading fails with authentication or authorization errors. Redirect validation moves into a shared trait, authorization failures now return handled access-denied responses, campaign statistics hide when authorization fails, and one bounces tab button class is updated.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SettingsController
  participant spa_html_twig as spa.html.twig
  participant VueRouter
  participant SettingsActionsPanel
  participant configClient
  participant adminClient
  participant adminAttributeClient

  Browser->>SettingsController: GET /settings
  SettingsController->>spa_html_twig: render SPA shell
  Browser->>VueRouter: resolve /settings
  VueRouter->>SettingsActionsPanel: mount settings UI
  User->>SettingsActionsPanel: switch tabs
  SettingsActionsPanel->>configClient: load/update configs
  SettingsActionsPanel->>adminClient: fetch/create/update/delete admins
  SettingsActionsPanel->>adminAttributeClient: fetch/create/update/delete attributes
Loading
sequenceDiagram
  participant Browser
  participant DashboardController
  participant spa_html_twig as spa.html.twig
  participant DashboardView

  Browser->>DashboardController: GET dashboard page
  DashboardController->>DashboardController: fetch stats or capture auth error
  DashboardController->>spa_html_twig: render dashboard_error
  Browser->>DashboardView: mount with data-dashboard-error
  DashboardView-->>Browser: show alert when error is present
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • phpList/web-frontend#73: Shares the Vue Router route configuration area and document-title behavior in assets/router/index.js.
  • phpList/web-frontend#76: Also changes the dashboard controller and dashboard view flow for server-to-SPA error data.
  • phpList/web-frontend#81: Also extends the Vue Router routes array in assets/router/index.js with a new top-level page route.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding a Settings page to the web frontend.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch settings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/components/settings/SettingsActionsPanel.vue`:
- Around line 15-18: Replace the deprecated Tailwind utility class
`flex-shrink-0` with the current Tailwind v4 convention `shrink-0` in the class
binding of the SettingsActionsPanel.vue component. The class is located in the
dynamic class attribute that changes based on the activeTab condition, so update
the static class string to use the new utility name for consistency with current
Tailwind standards.

In `@assets/vue/components/settings/SettingsConfigs.vue`:
- Around line 27-71: The SettingsConfigs.vue component has isLoading and error
state variables defined in the script but they are not rendered in the template,
causing users to see a misleading "No configuration keys found" message during
fetch failures or loading. Update the template section to add conditional
rendering that checks isLoading and error states before rendering the
configuration items grid. Add a loading indicator or message when isLoading is
true, display the error message when error is set, and only show the "No
configuration keys found" message when filtered.length is empty and there is no
loading or error state. Use v-if directives to control the visibility of these
different states in the appropriate order.
- Around line 40-44: The input element using v-model="edited[item.key]" lacks an
associated label, making it inaccessible to screen reader users. Add a label
element that explicitly identifies the configuration setting being edited.
Either wrap the input with a label element or add a separate label with a for
attribute pointing to an id on the input. The label text should reference
item.key or the setting name so users can identify which configuration value
they are editing.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 957e3370-b693-425a-90e6-9931b2c1cbf6

📥 Commits

Reviewing files that changed from the base of the PR and between 0f19688 and d31d73e.

📒 Files selected for processing (7)
  • assets/router/index.js
  • assets/vue/components/settings/SettingsActionsPanel.vue
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • assets/vue/components/settings/SettingsAdmins.vue
  • assets/vue/components/settings/SettingsConfigs.vue
  • assets/vue/views/SettingsView.vue
  • src/Controller/SettingsController.php

Comment thread assets/vue/components/settings/SettingsActionsPanel.vue Outdated
Comment thread assets/vue/components/settings/SettingsConfigs.vue
Comment thread assets/vue/components/settings/SettingsConfigs.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/components/settings/SettingsAdmins.vue`:
- Around line 208-222: The confirmation dialog in the handleDelete function
references admin.login_name, but the admin object uses camelCase properties as
evidenced by the template usage of admin.loginName. Change the property
reference from admin.login_name to admin.loginName in the window.confirm message
to ensure the confirmation dialog displays the actual administrator login name
instead of undefined.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c8984b02-b139-45f8-b66f-6d49d1e8ae26

📥 Commits

Reviewing files that changed from the base of the PR and between d31d73e and 4bac561.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • assets/vue/api.js
  • assets/vue/components/bounces/BouncesActionsPanel.vue
  • assets/vue/components/settings/CreateAdminModal.vue
  • assets/vue/components/settings/EditAdminModal.vue
  • assets/vue/components/settings/SettingsActionsPanel.vue
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • assets/vue/components/settings/SettingsAdmins.vue
  • assets/vue/components/settings/SettingsConfigs.vue
  • assets/vue/views/SettingsView.vue
  • package.json
✅ Files skipped from review due to trivial changes (2)
  • assets/vue/components/bounces/BouncesActionsPanel.vue
  • assets/vue/views/SettingsView.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • assets/vue/components/settings/SettingsActionsPanel.vue

Comment thread assets/vue/components/settings/SettingsAdmins.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/components/settings/EditAdminModal.vue`:
- Around line 17-18: The close button in EditAdminModal.vue contains only an SVG
icon without a text label or aria-label, making it inaccessible to screen
readers. Add an aria-label attribute to the button element that triggers the
close method to provide an accessible name. The button should have aria-label
set to a descriptive value like "Close" so assistive technology users can
understand the button's purpose.
- Around line 211-218: The watch function currently only observes the isOpen
prop, causing the form to retain stale values when a different admin is selected
while the modal is already open. Modify the watch function to observe both
props.isOpen and props.admin as dependencies, and ensure resetForm() is called
whenever either the modal opens (isOpen becomes true) or the admin selection
changes, preventing stale data from being submitted for the wrong admin.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90cc9b2e-5936-44e2-92b5-f1b9230d5f2f

📥 Commits

Reviewing files that changed from the base of the PR and between 4bac561 and bccb908.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • assets/vue/components/settings/CreateAdminModal.vue
  • assets/vue/components/settings/EditAdminModal.vue
  • assets/vue/components/settings/SettingsAdmins.vue
  • package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • package.json
  • assets/vue/components/settings/SettingsAdmins.vue
  • assets/vue/components/settings/CreateAdminModal.vue

Comment thread assets/vue/components/settings/EditAdminModal.vue Outdated
Comment thread assets/vue/components/settings/EditAdminModal.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
assets/vue/components/settings/CreateAdminAttributeModal.vue (1)

13-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tiny a11y nicety: label the close control.

The button reads as a literal glyph to screen readers. A quick aria-label makes the modal friendlier to navigate.

♿ Suggested tweak
           <button
+              type="button"
+              aria-label="Close"
               class="text-slate-400 hover:text-slate-700"
               `@click`="$emit('close')"
           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/settings/CreateAdminAttributeModal.vue` around lines 13
- 18, The close control in CreateAdminAttributeModal.vue is currently exposed
only as a glyph, so screen readers won’t know its purpose. Update the button
that emits the close event to include a clear accessible label using an
aria-label on the same button element. Keep the existing click behavior
unchanged and make sure the label describes that it closes the modal.
assets/vue/components/campaigns/CampaignDirectory.vue (1)

739-808: 🩺 Stability & Availability | 🔵 Trivial

Solid auth handling — just tighten the 403 guard and consider isolating stats errors

  1. Add error?.response?.status === 403 to the auth check.
    You currently check error?.status, but assets/vue/api.js (line 23) establishes error?.response?.status as the canonical field for HTTP status codes in this SPA. If the client wraps 403s there, your check misses it, the exception falls through to throw error, and the entire campaigns list vanishes.

    Add this to the condition:

    || error?.response?.status === 403
  2. Optionally, let stats glitches not trash the campaigns list.
    Since fetchCampaignStatistics runs inside a Promise.all in fetchCampaigns, any non-auth error you rethrow collapses the whole allCampaigns result—even if the campaigns themselves loaded perfectly. Now that auth errors already degrade gracefully, you might want other stats errors to do the same (hide stats, keep the list) instead of nucking the page.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/campaigns/CampaignDirectory.vue` around lines 739 -
808, The auth guard in the campaign statistics fetch is missing the wrapped HTTP
status shape used by the API client. Update the catch block in
fetchCampaignStatistics within CampaignDirectory.vue to treat
error?.response?.status === 403 the same as the existing AuthorizationException
checks, and then keep the fallback behavior of clearing statisticsByCampaignId
and hiding showStatistics. If you want to avoid losing the campaigns list on
non-auth stats failures, handle those errors locally in fetchCampaignStatistics
instead of rethrowing them so fetchCampaigns and Promise.all can still succeed.
src/Controller/DashboardController.php (1)

27-32: 🩺 Stability & Availability | 🔵 Trivial

Heads up: only auth/authz failures land in the banner.

The catch block only handles AuthenticationException | AuthorizationException. Most HTTP client libraries (like the one backing StatisticsClient) also throw separate exceptions for network blips, 5xx responses, or malformed payloads. Those would bubble up and show a generic error page instead of your friendly dashboard banner.

If that narrow scope is intentional, no worries. Otherwise, consider widening the catch to handle general failures gracefully alongside auth issues.

Wish I could grep the PhpList\RestApiClient source for exact exception types, but it's not in the sandbox. A quick look at the library docs or your local vendor/ folder should confirm what slips past the current catch.

Original code context
try {
    $stats = $this->statisticsClient->getDashboardStats();
    $dashboardStats = $this->buildDashboardStats($stats);
} catch (AuthenticationException | AuthorizationException $e) {
    $dashboardError = $e->getMessage() ?: 'Unable to load dashboard statistics.';
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Controller/DashboardController.php` around lines 27 - 32, The dashboard
stats fetch in DashboardController::show currently catches only
AuthenticationException and AuthorizationException, so other client failures can
still break the page. Broaden the exception handling around
StatisticsClient::getDashboardStats() and buildDashboardStats() to also catch
the relevant transport/runtime failures (or a general failure type) and route
them to the same $dashboardError banner path. Keep the auth/authz handling
intact, but make sure unexpected HTTP/network/parse errors are converted into
the friendly dashboard message instead of bubbling up.
tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php (1)

82-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the non-XHR authorization branch as well.

The production branch at UnauthorizedSubscriber Line 50 returns a plain 403 response, but this test only exercises the JSON/XHR path.

Suggested additional test
     public function testOnKernelExceptionWithAuthorizationException(): void
     {
         $authException = new AuthorizationException('No valid session key was provided as basic auth password.', 403);
 
         ...
         $this->assertEquals('No valid session key was provided as basic auth password.', $data['message']);
     }
+
+    public function testOnKernelExceptionWithAuthorizationExceptionForBrowserRequest(): void
+    {
+        $authException = new AuthorizationException('Forbidden', 403);
+
+        $request = $this->createMock(Request::class);
+        $request->method('isXmlHttpRequest')->willReturn(false);
+
+        $kernel = $this->createMock(HttpKernelInterface::class);
+        $event = new ExceptionEvent(
+            $kernel,
+            $request,
+            HttpKernelInterface::MAIN_REQUEST,
+            $authException
+        );
+
+        $this->subscriber->onKernelException($event);
+
+        $response = $event->getResponse();
+        $this->assertNotNull($response);
+        $this->assertEquals(403, $response->getStatusCode());
+        $this->assertEquals('Access denied.', $response->getContent());
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php` around lines 82 -
112, The current UnauthorizedSubscriberTest only covers the XHR/JSON
authorization path in onKernelExceptionWithAuthorizationException, so add a
separate assertion case for the non-XHR branch. Reuse AuthorizationException,
ExceptionEvent, and UnauthorizedSubscriber::onKernelException with
Request::isXmlHttpRequest returning false, then verify the response is a plain
403 response rather than a JsonResponse and that the status code is still 403.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/components/settings/CreateAdminAttributeModal.vue`:
- Around line 42-51: The type selector in CreateAdminAttributeModal.vue is using
values that are not accepted by AdminAttributeDefinitionRequest.type. Update the
<select> options and the form default for form.type so they only use the API
enum values textline and hidden, and make sure the v-model binding and any
related initialization in the component stay consistent with those allowed
values.

In `@assets/vue/components/settings/EditAdminAttributeModal.vue`:
- Around line 42-51: The EditAdminAttributeModal select is using UI
labels/values that do not match the API enum, so existing values set by the form
watcher can render blank and be overwritten on save. Update the options in
EditAdminAttributeModal.vue to use the same enum values as the API, and make
sure the form.type binding in the edit flow continues to round-trip correctly
through the watcher and save handler.

In `@src/EventSubscriber/UnauthorizedSubscriber.php`:
- Around line 38-50: In UnauthorizedSubscriber::onKernelException, stop reusing
AuthorizationException::getMessage() for the user-facing 403 body; always return
a generic “Access denied.” message for both the JsonResponse and the plain
Response, while keeping any detailed upstream text out of the response. Update
the branch that handles $event->getRequest()->isXmlHttpRequest() and the
non-JSON Response path so they both use the same safe fallback, and adjust the
matching test assertion to expect the generic message instead of the exception
text.

---

Nitpick comments:
In `@assets/vue/components/campaigns/CampaignDirectory.vue`:
- Around line 739-808: The auth guard in the campaign statistics fetch is
missing the wrapped HTTP status shape used by the API client. Update the catch
block in fetchCampaignStatistics within CampaignDirectory.vue to treat
error?.response?.status === 403 the same as the existing AuthorizationException
checks, and then keep the fallback behavior of clearing statisticsByCampaignId
and hiding showStatistics. If you want to avoid losing the campaigns list on
non-auth stats failures, handle those errors locally in fetchCampaignStatistics
instead of rethrowing them so fetchCampaigns and Promise.all can still succeed.

In `@assets/vue/components/settings/CreateAdminAttributeModal.vue`:
- Around line 13-18: The close control in CreateAdminAttributeModal.vue is
currently exposed only as a glyph, so screen readers won’t know its purpose.
Update the button that emits the close event to include a clear accessible label
using an aria-label on the same button element. Keep the existing click behavior
unchanged and make sure the label describes that it closes the modal.

In `@src/Controller/DashboardController.php`:
- Around line 27-32: The dashboard stats fetch in DashboardController::show
currently catches only AuthenticationException and AuthorizationException, so
other client failures can still break the page. Broaden the exception handling
around StatisticsClient::getDashboardStats() and buildDashboardStats() to also
catch the relevant transport/runtime failures (or a general failure type) and
route them to the same $dashboardError banner path. Keep the auth/authz handling
intact, but make sure unexpected HTTP/network/parse errors are converted into
the friendly dashboard message instead of bubbling up.

In `@tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php`:
- Around line 82-112: The current UnauthorizedSubscriberTest only covers the
XHR/JSON authorization path in onKernelExceptionWithAuthorizationException, so
add a separate assertion case for the non-XHR branch. Reuse
AuthorizationException, ExceptionEvent, and
UnauthorizedSubscriber::onKernelException with Request::isXmlHttpRequest
returning false, then verify the response is a plain 403 response rather than a
JsonResponse and that the status code is still 403.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c6989a8-8151-42e9-870b-fdd833dc078a

📥 Commits

Reviewing files that changed from the base of the PR and between bccb908 and 0d92119.

📒 Files selected for processing (19)
  • assets/vue/api.js
  • assets/vue/components/campaigns/CampaignDirectory.vue
  • assets/vue/components/settings/CreateAdminAttributeModal.vue
  • assets/vue/components/settings/CreateAdminModal.vue
  • assets/vue/components/settings/EditAdminAttributeModal.vue
  • assets/vue/components/settings/EditAdminModal.vue
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • assets/vue/views/DashboardView.spec.js
  • assets/vue/views/DashboardView.vue
  • src/Controller/AuthController.php
  • src/Controller/BaseController.php
  • src/Controller/DashboardController.php
  • src/EventSubscriber/AuthGateSubscriber.php
  • src/EventSubscriber/UnauthorizedSubscriber.php
  • src/Security/SessionAuthenticator.php
  • src/Trait/RedirectValidationTrait.php
  • templates/spa.html.twig
  • tests/Integration/Controller/DashboardControllerTest.php
  • tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • assets/vue/components/settings/EditAdminModal.vue
  • assets/vue/components/settings/CreateAdminModal.vue

Comment thread assets/vue/components/settings/CreateAdminAttributeModal.vue
Comment thread assets/vue/components/settings/EditAdminAttributeModal.vue
Comment thread src/EventSubscriber/UnauthorizedSubscriber.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
assets/vue/components/settings/EditSubscriberAttributeModal.vue (1)

100-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tiny nit: the option name input here has no placeholder, while the create modal uses "Name" (its order input also has an "Order" placeholder this one lacks). Aligning them keeps the two modals consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/settings/EditSubscriberAttributeModal.vue` at line 100,
The option name input in the EditSubscriberAttributeModal is missing a
placeholder, unlike the corresponding field in the create modal, so update the
input in this component to use the same “Name” placeholder (and keep its sibling
“Order” field consistent as well) by aligning the relevant template fields in
EditSubscriberAttributeModal with the create-modal inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/components/settings/CreateSubscriberAttributeModal.vue`:
- Around line 150-152: The create modal is using the wrong API client:
`CreateSubscriberAttributeModal.vue` imports `adminAttributeClient` even though
this flow is for subscriber attributes. Update the modal to use
`subscriberAttributesClient` (matching `SettingsSubscriberAttributes.vue`) and
call its `createAttributeDefinition(...)` method so newly created attributes are
written to the same endpoint that the list and delete actions use. Confirm the
request payload still matches the subscriber-attribute client’s expected shape
and keep the surrounding `createAttributeDefinition` submit flow intact.

In `@assets/vue/components/settings/EditSubscriberAttributeModal.vue`:
- Around line 148-150: The edit modal is using the wrong API client, so updates
go through adminAttributeClient instead of the subscriber attributes client.
Update the EditSubscriberAttributeModal.vue flow to use the same
subscriberAttributesClient as the rest of the subscriber attribute actions,
especially the updateAttributeDefinition path, and make sure any related methods
in this component reference the corrected client consistently.

In `@assets/vue/components/settings/SettingsSubscriberAttributes.vue`:
- Around line 243-257: The current fetchAttributes implementation only loads the
first page of subscriber attributes because it calls getAttributeDefinitions()
without paging, so update it to use the existing fetchAllAttributeDefinitions
helper from assets/vue/api.js instead. Keep the same loading/error state
handling in fetchAttributes, but replace the single request with the paginated
helper so attributes.value is populated with the full complete list rather than
only result.items/result.data from the first page.

---

Nitpick comments:
In `@assets/vue/components/settings/EditSubscriberAttributeModal.vue`:
- Line 100: The option name input in the EditSubscriberAttributeModal is missing
a placeholder, unlike the corresponding field in the create modal, so update the
input in this component to use the same “Name” placeholder (and keep its sibling
“Order” field consistent as well) by aligning the relevant template fields in
EditSubscriberAttributeModal with the create-modal inputs.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d161459b-7263-44cb-9917-4e7eda08b9c1

📥 Commits

Reviewing files that changed from the base of the PR and between 0d92119 and 62961a9.

📒 Files selected for processing (9)
  • assets/vue/components/settings/CreateAdminAttributeModal.vue
  • assets/vue/components/settings/CreateSubscriberAttributeModal.vue
  • assets/vue/components/settings/EditAdminAttributeModal.vue
  • assets/vue/components/settings/EditSubscriberAttributeModal.vue
  • assets/vue/components/settings/SettingsActionsPanel.vue
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • assets/vue/components/settings/SettingsSubscriberAttributes.vue
  • src/EventSubscriber/UnauthorizedSubscriber.php
  • tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php
  • assets/vue/components/settings/CreateAdminAttributeModal.vue
  • assets/vue/components/settings/EditAdminAttributeModal.vue
  • assets/vue/components/settings/SettingsActionsPanel.vue
  • assets/vue/components/settings/SettingsAdminAttributes.vue
  • src/EventSubscriber/UnauthorizedSubscriber.php

Comment on lines +150 to +152
<script setup>
import { computed, reactive, ref, watch } from 'vue';
import { adminAttributeClient } from '../../api';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Wrong client — this writes to admin attributes, not subscriber attributes. 🐇

This is the subscriber-attribute create modal, but it imports and calls adminAttributeClient.createAttributeDefinition(...). The parent SettingsSubscriberAttributes.vue reads and deletes through subscriberAttributesClient, so created records land on a different endpoint than the ones being listed/deleted. Net effect: you create something, the list refreshes, and your new attribute is nowhere to be seen.

🔧 Proposed fix
-import { adminAttributeClient } from '../../api';
+import { subscriberAttributesClient } from '../../api';
-    await adminAttributeClient.createAttributeDefinition({
+    await subscriberAttributesClient.createAttributeDefinition({
       ...form,
       options: JSON.parse(JSON.stringify(form.options)),
     });

Worth confirming subscriberAttributesClient exposes createAttributeDefinition with this payload shape.

#!/bin/bash
# Confirm SubscriberAttributesClient exposes create/update method names used here
fd -t f -e js . node_modules/@tatevikgr/rest-api-client 2>/dev/null | head
rg -nP 'class\s+SubscriberAttributesClient' -A40 $(fd . node_modules/@tatevikgr/rest-api-client -e js 2>/dev/null) 2>/dev/null
rg -nP '(create|update|delete|get)AttributeDefinition' -C2 $(fd . node_modules/@tatevikgr/rest-api-client -e js 2>/dev/null) 2>/dev/null

Also applies to: 224-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/settings/CreateSubscriberAttributeModal.vue` around
lines 150 - 152, The create modal is using the wrong API client:
`CreateSubscriberAttributeModal.vue` imports `adminAttributeClient` even though
this flow is for subscriber attributes. Update the modal to use
`subscriberAttributesClient` (matching `SettingsSubscriberAttributes.vue`) and
call its `createAttributeDefinition(...)` method so newly created attributes are
written to the same endpoint that the list and delete actions use. Confirm the
request payload still matches the subscriber-attribute client’s expected shape
and keep the surrounding `createAttributeDefinition` submit flow intact.

Comment on lines +148 to +150
<script setup>
import { computed, reactive, ref, watch } from 'vue';
import { adminAttributeClient } from '../../api';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Same client mix-up here — update hits adminAttributeClient.

Same root cause as the create modal: this edit modal updates via adminAttributeClient.updateAttributeDefinition(...), while the parent lists/deletes through subscriberAttributesClient. Edits will silently target the wrong resource.

🔧 Proposed fix
-import { adminAttributeClient } from '../../api';
+import { subscriberAttributesClient } from '../../api';
-    await adminAttributeClient.updateAttributeDefinition(
+    await subscriberAttributesClient.updateAttributeDefinition(
         form.id,
         JSON.parse(JSON.stringify(form))
     );

Also applies to: 224-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/settings/EditSubscriberAttributeModal.vue` around lines
148 - 150, The edit modal is using the wrong API client, so updates go through
adminAttributeClient instead of the subscriber attributes client. Update the
EditSubscriberAttributeModal.vue flow to use the same subscriberAttributesClient
as the rest of the subscriber attribute actions, especially the
updateAttributeDefinition path, and make sure any related methods in this
component reference the corrected client consistently.

Comment on lines +243 to +257
const fetchAttributes = async () => {
isLoading.value = true
loadError.value = ''

try {
const result = await subscriberAttributesClient.getAttributeDefinitions()
attributes.value = result.items ?? result.data ?? []
} catch (e) {
console.error(e)
loadError.value = e?.message || 'Failed to load attributes.'
attributes.value = []
} finally {
isLoading.value = false
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Heads up: this only grabs the first page of attributes.

getAttributeDefinitions() is called with no cursor/limit, so anything past the first page silently won't show up. There's already a fetchAllAttributeDefinitions helper in assets/vue/api.js (lines 134-154) that handles the cursor pagination loop — worth reusing here so the list stays complete as definitions grow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/components/settings/SettingsSubscriberAttributes.vue` around lines
243 - 257, The current fetchAttributes implementation only loads the first page
of subscriber attributes because it calls getAttributeDefinitions() without
paging, so update it to use the existing fetchAllAttributeDefinitions helper
from assets/vue/api.js instead. Keep the same loading/error state handling in
fetchAttributes, but replace the single request with the paginated helper so
attributes.value is populated with the full complete list rather than only
result.items/result.data from the first page.

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