Repository: PrestaShop/autoupgrade
Pull request #1313: [NEW UI] Add next step info + confirmation dialog
Author: login: tblivet
Labels: name: QA ✔️
description: Status: Check done, Code approved
name: new feature
description: Type: New feature
Created: 2025-04-14T09:35:56Z | Merged: 2025-04-16T14:13:28Z

<!-----------------------------------------------------------------------------
Thank you for contributing to the PrestaShop project! 
Please take the time to edit the "Answers" rows below with the necessary information.
Check out our contribution guidelines to find out how to complete it:
https://devdocs.prestashop-project.org/8/contribute/contribution-guidelines/#pull-requests
------------------------------------------------------------------------------>

| Questions         | Answers
| ------------------| -------------------------------------------------------
| Description?      | Add new line in next step info on the post update page with a confirmation dialog
| Type?             | new feature
| BC breaks?        | no
| Deprecations?     | no
| Fixed ticket?     | --
| Sponsor company   | @PrestaShopCorp
| How to test?      | --

- Run a successful update. On the post-update page, you should see a new line in the Next Step section that says:
"Check the Module Manager to discover and install the modules extracted on your server during the update process."
- When you click on the "Module Manager" link, a dialog should open.
- Inside the dialog, if you click the "Go to Module Manager" link, you'll be redirected to the login page.
- After logging in, you should be redirected to the Module Manager page.



Diff:
diff --git a/_dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts b/_dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts
index 4089a40478..48eb9f181b 100644
--- a/_dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts
+++ b/_dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts
@@ -17,11 +17,39 @@
  * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
  */
 import StepPage from './StepPage';
+import api from '../api/RequestHandler';
 
 export default class UpdatePagePostUpdate extends StepPage {
   protected stepCode = 'post-update';
 
   public mount() {
     this.initStepper();
+    this.#dialogConfirmModuleManagerLink.addEventListener('click', this.#onClickDialogLink);
+  }
+
+  public beforeDestroy() {
+    this.#dialogConfirmModuleManagerLink.removeEventListener('click', this.#onClickDialogLink);
+  }
+
+  #onClickDialogLink = async (event: MouseEvent) => {
+    const target = event.target as HTMLAnchorElement;
+
+    // Checks if the clicked element is an <a> tag pointing towards an ID
+    if (!target || target.tagName !== 'A' || !target.hash) {
+      return;
+    }
+
+    event.preventDefault();
+
+    const hashRoute = target.hash.substring(1);
+    await api.post(hashRoute);
+  };
+
+  get #dialogConfirmModuleManagerLink(): HTMLAnchorElement {
+    const link = document.getElementById('dialog-confirm-module-manager-link');
+    if (!link) {
+      throw new Error('Dialog trigger link ID was not found');
+    }
+    return link as HTMLAnchorElement;
   }
 }
diff --git a/classes/Router/Routes.php b/classes/Router/Routes.php
index 1c4dc02ab9..663126a23a 100644
--- a/classes/Router/Routes.php
+++ b/classes/Router/Routes.php
@@ -60,6 +60,7 @@ class Routes
     /* step: post update */
     const UPDATE_PAGE_POST_UPDATE = 'update-page-post-update';
     const UPDATE_STEP_POST_UPDATE = 'update-step-post-update';
+    const UPDATE_STEP_POST_UPDATE_CONFIRM_MODULE_MANAGER_DIALOG = 'update-step-post-update-confirm-module-manager-dialog';
 
     /* RESTORE PAGE */
     /* step: backup selection */
diff --git a/classes/Router/RoutesConfig.php b/classes/Router/RoutesConfig.php
index d76926e700..aa32191fbe 100644
--- a/classes/Router/RoutesConfig.php
+++ b/classes/Router/RoutesConfig.php
@@ -169,6 +169,10 @@ class RoutesConfig
                 'controller' => UpdatePagePostUpdateController::class,
                 'method' => 'step',
             ],
+            Routes::UPDATE_STEP_POST_UPDATE_CONFIRM_MODULE_MANAGER_DIALOG => [
+                'controller' => UpdatePagePostUpdateController::class,
+                'method' => 'confirmModuleManagerDialog',
+            ],
             /* RESTORE PAGE */
             /* step: backup selection */
             Routes::RESTORE_PAGE_BACKUP_SELECTION => [
diff --git a/controllers/admin/self-managed/UpdatePagePostUpdateController.php b/controllers/admin/self-managed/UpdatePagePostUpdateController.php
index 83f4866a51..527a3cc356 100644
--- a/controllers/admin/self-managed/UpdatePagePostUpdateController.php
+++ b/controllers/admin/self-managed/UpdatePagePostUpdateController.php
@@ -21,13 +21,17 @@
 
 namespace PrestaShop\Module\AutoUpgrade\Controller;
 
+use Context;
+use PrestaShop\Module\AutoUpgrade\AjaxResponseBuilder;
 use PrestaShop\Module\AutoUpgrade\DocumentationLinks;
 use PrestaShop\Module\AutoUpgrade\Router\Routes;
 use PrestaShop\Module\AutoUpgrade\Task\TaskType;
+use PrestaShop\Module\AutoUpgrade\Twig\PageSelectors;
 use PrestaShop\Module\AutoUpgrade\Twig\Steps\Stepper;
 use PrestaShop\Module\AutoUpgrade\Twig\Steps\UpdateSteps;
 use PrestaShop\Module\AutoUpgrade\UpgradeContainer;
 use PrestaShop\Module\AutoUpgrade\VersionUtils;
+use Symfony\Component\HttpFoundation\JsonResponse;
 
 class UpdatePagePostUpdateController extends AbstractPageWithStepController
 {
@@ -66,7 +70,24 @@ protected function getParams(): array
                 'dev_doc_link' => DocumentationLinks::getDevDocUpdateAssistantPostUpdateUrl($currentMajorVersion),
                 'download_logs' => $this->upgradeContainer->getLogsService()->getDownloadLogsData(TaskType::TASK_TYPE_UPDATE),
                 'ps_version' => $this->upgradeContainer->getCurrentPrestaShopVersion(),
+                'form_route_to_confirm_module_manager_dialog' => Routes::UPDATE_STEP_POST_UPDATE_CONFIRM_MODULE_MANAGER_DIALOG,
             ]
         );
     }
+
+    public function confirmModuleManagerDialog(): JsonResponse
+    {
+        $this->upgradeContainer->initPrestaShopCore();
+        $moduleManagerLink = Context::getContext()->link->getAdminLink('AdminModules');
+
+        return AjaxResponseBuilder::hydrationResponse(
+            PageSelectors::DIALOG_PARENT_ID,
+            $this->getTwig()->render(
+                '@ModuleAutoUpgrade/dialogs/dialog-confirm-module-manager.html.twig',
+                [
+                    'module_manager_link' => $moduleManagerLink,
+                ]
+            )
+        );
+    }
 }
diff --git a/storybook/stories/components/DialogConfirmModuleManager.stories.js b/storybook/stories/components/DialogConfirmModuleManager.stories.js
new file mode 100644
index 0000000000..33f46a99e4
--- /dev/null
+++ b/storybook/stories/components/DialogConfirmModuleManager.stories.js
@@ -0,0 +1,36 @@
+/**
+ * Copyright since 2007 PrestaShop SA and Contributors
+ * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Academic Free License version 3.0
+ * that is bundled with this package in the file LICENSE.md.
+ * It is also available through the world-wide-web at this URL:
+ * https://opensource.org/licenses/AFL-3.0
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@prestashop.com so we can send you a copy immediately.
+ *
+ * @author    PrestaShop SA and Contributors <contact@prestashop.com>
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
+ */
+
+import DialogConfirmModuleManager from "../../../views/templates/dialogs/dialog-confirm-module-manager.html.twig";
+
+export default {
+  title: "Components/Dialog",
+  component: DialogConfirmModuleManager,
+};
+
+export const ConfirmModuleManager = {
+  args: {
+    form_route_to_confirm_module_manager_dialog: "/",
+    module_manager_link: "/",
+  },
+  play: async () => {
+    const dialog = document.querySelector(".dialog");
+    dialog.showModal();
+  },
+};
diff --git a/storybook/stories/pages/PostUpdate.stories.js b/storybook/stories/pages/PostUpdate.stories.js
index 0a4beb4717..a2caebe199 100644
--- a/storybook/stories/pages/PostUpdate.stories.js
+++ b/storybook/stories/pages/PostUpdate.stories.js
@@ -38,6 +38,9 @@ export const PostUpdate = {
     dev_doc_link: "#",
     data_transparency_link:
       "https://www.prestashop-project.org/data-transparency",
+    admin_url: "#",
+    ps_version: "9.0.0",
+    form_route_to_confirm_module_manager_dialog: "#",
     // Stepper
     ...Stepper.args,
   },
diff --git a/views/templates/dialogs/dialog-confirm-module-manager.html.twig b/views/templates/dialogs/dialog-confirm-module-manager.html.twig
new file mode 100644
index 0000000000..b902f76400
--- /dev/null
+++ b/views/templates/dialogs/dialog-confirm-module-manager.html.twig
@@ -0,0 +1,40 @@
+{#**
+ * Copyright since 2007 PrestaShop SA and Contributors
+ * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Academic Free License version 3.0
+ * that is bundled with this package in the file LICENSE.md.
+ * It is also available through the world-wide-web at this URL:
+ * https://opensource.org/licenses/AFL-3.0
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@prestashop.com so we can send you a copy immediately.
+ *
+ * @author    PrestaShop SA and Contributors <contact@prestashop.com>
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
+ *#}
+{% extends "@ModuleAutoUpgrade/components/dialog.html.twig" %}
+
+{% set title = 'Access the Module Manager?'|trans({}) %}
+{% set dialogSize = 'sm' %}
+{% set message = 'To reach the module manager, you will be logged out of your session and will have to log in again. Do you want to continue?'|trans({}) %}
+
+{% block dialog_content %}
+  {{ parent() }}
+{% endblock %}
+
+{% block dialog_extra_content %}{% endblock %}
+
+{% block dialog_footer_inner %}
+  <button type="button" class="btn btn-link" data-dismiss="dialog">
+    {{ 'Cancel'|trans({}) }}
+  </button>
+
+  <a class="btn btn-primary" href="{{ module_manager_link }}">
+    <i class="material-icons">exit_to_app</i>
+    {{ 'Go to Module Manager'|trans({}) }}
+  </a>
+{% endblock %}
diff --git a/views/templates/steps/post-update.html.twig b/views/templates/steps/post-update.html.twig
index 323dbcc0ed..51c545be4c 100644
--- a/views/templates/steps/post-update.html.twig
+++ b/views/templates/steps/post-update.html.twig
@@ -62,6 +62,14 @@
             '%endbold%': '</b>'
           })|raw }}
         </li>
+        <li>
+          {{ '%startbold%Check the %startlink%Module Manager%endlink%%endbold% to discover and install the modules extracted on your server during the update process.'|trans({
+            '%startbold%': '<b>',
+            '%endbold%': '</b>',
+            '%startlink%': '<a id="dialog-confirm-module-manager-link" class="link" href="#' ~ form_route_to_confirm_module_manager_dialog ~ '">',
+            '%endlink%': '</a>'
+          })|raw }}
+        </li>
       </ul>
     </div>
 


Reviews:
user: login: Quetzacoalt91
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T13:58:23Z
user: login: ga-devfront
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T14:11:21Z
user: login: tblivet
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T14:34:09Z
user: login: ga-devfront
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T14:38:47Z
user: login: Quetzacoalt91
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T14:40:25Z
user: login: AureRita
body: Hi @tblivet 

Thank you for your PR, I tested it and it seems to works as you can see : 

https://github.com/user-attachments/assets/2e67b819-98cb-434e-9814-7859acf1eb91

![Capture d’écran du 2025-04-16 11-48-04](https://github.com/user-attachments/assets/75694a30-cdc2-4540-8507-67293c1a1581)

![Capture d’écran du 2025-04-16 11-50-08](https://github.com/user-attachments/assets/8b5a6869-7b0d-4cdf-82a5-a26913d08f6d)

Tested from : 
8.0.5 to 8.1.4 
8.1.4 to 8.2.1

Because the PR seems to works as expected, It's QA :heavy_check_mark: 

Thank you
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T10:09:08Z

Review comments:
diff_hunk: @@ -17,11 +17,43 @@
  * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
  */
 import StepPage from './StepPage';
+import api from '../api/RequestHandler';
 
 export default class UpdatePagePostUpdate extends StepPage {
   protected stepCode = 'post-update';
 
   public mount() {
     this.initStepper();
+    this.#handleHydrate();
+  }
+
+  #handleHydrate = () => {
+    this.#addListenerToDialogConfirmModuleManagerLink();
+  };
+
+  #onClickDialogLink = async (event: MouseEvent) => {
+    const target = event.target as HTMLAnchorElement;
+
+    // Checks if the clicked element is an <a> tag pointing towards an ID
+    if (!target || target.tagName !== 'A' || !target.hash) {
+      return;
+    }
+
+    event.preventDefault();
+
+    const hashRoute = target.hash.substring(1);
+    await api.post(hashRoute);
+  };
+
+  #addListenerToDialogConfirmModuleManagerLink = () => {
+    this.#dialogConfirmModuleManagerLink.addEventListener('click', this.#onClickDialogLink);
path: _dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts
user: login: Quetzacoalt91
body: I usually find a equivalent `removeEventListener` called by the beforeDestroy event
created_at: 2025-04-14T13:44:30Z
author_association: MEMBER
diff_hunk: @@ -62,6 +62,14 @@
             '%endbold%': '</b>'
           })|raw }}
         </li>
+        <li>
+          {{ '%startbold%Check the %startlink%Module Manager%endlink%%endbold% to discover and install the modules extracted on your server during the update process.'|trans({
+            '%startbold%': '<b>',
+            '%endbold%': '</b>',
+            '%startlink%': '<a id="dialog-confirm-module-manager-link" class="link" href="#' ~ form_route_to_confirm_module_manager_dialog ~ '">',
+            '%endlink%': '</a>'
path: views/templates/steps/post-update.html.twig
user: login: ga-devfront
body: ```suggestion
          {{ '[1]Check the [2]Module Manager[/2][/1] to discover and install the modules extracted on your server during the update process.'|trans({
            '[1]': '<b>',
            '[/1]': '</b>',
            '[2]': '<a id="dialog-confirm-module-manager-link" class="link" href="#' ~ form_route_to_confirm_module_manager_dialog ~ '">',
            '[/2]': '</a>'
```
I think using numbered tags will be more readable for future contributors and on crowdin.
created_at: 2025-04-14T14:06:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,11 +17,43 @@
  * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
  */
 import StepPage from './StepPage';
+import api from '../api/RequestHandler';
 
 export default class UpdatePagePostUpdate extends StepPage {
   protected stepCode = 'post-update';
 
   public mount() {
     this.initStepper();
+    this.#handleHydrate();
+  }
+
+  #handleHydrate = () => {
+    this.#addListenerToDialogConfirmModuleManagerLink();
+  };
path: _dev/src/ts/appUI/pages/UpdatePagePostUpdate.ts
user: login: ga-devfront
body: Why create a method who used another method who do only one thing ? I think you can add directly the addEventListner directly inside the mount() and don't forget to removeEventListner in beforeDestroy()
created_at: 2025-04-14T14:10:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,6 +62,14 @@
             '%endbold%': '</b>'
           })|raw }}
         </li>
+        <li>
+          {{ '%startbold%Check the %startlink%Module Manager%endlink%%endbold% to discover and install the modules extracted on your server during the update process.'|trans({
+            '%startbold%': '<b>',
+            '%endbold%': '</b>',
+            '%startlink%': '<a id="dialog-confirm-module-manager-link" class="link" href="#' ~ form_route_to_confirm_module_manager_dialog ~ '">',
+            '%endlink%': '</a>'
path: views/templates/steps/post-update.html.twig
user: login: tblivet
body: The rest of this file is handled using %xx%, so we'll stick with it!
created_at: 2025-04-14T14:34:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-14T14:37:34Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=PrestaShop_autoupgrade&pullRequest=1313) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [17.5% Duplication on New Code](https://sonarcloud.io/component_measures?id=PrestaShop_autoupgrade&pullRequest=1313&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=PrestaShop_autoupgrade&pullRequest=1313)


author_association: NONE