diff --git a/_dev/eslint.config.mjs b/_dev/eslint.config.mjs index e422aa09b..0e7c43111 100644 --- a/_dev/eslint.config.mjs +++ b/_dev/eslint.config.mjs @@ -27,7 +27,8 @@ export default [ sourceType: 'module', globals: { ...globals.browser, - ...globals.node + ...globals.node, + ...globals.jquery }, parser: tseslintParser, parserOptions: { diff --git a/_dev/package-lock.json b/_dev/package-lock.json index 234f4e769..5415bf15e 100644 --- a/_dev/package-lock.json +++ b/_dev/package-lock.json @@ -12,7 +12,9 @@ "devDependencies": { "@eslint/js": "^9.9.1", "@prestashopcorp/stylelint-config": "^1.0.0", + "@types/bootstrap": "^3.4.0", "@types/jest": "^29.5.13", + "@types/jquery": "^3.5.32", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "eslint": ">=9.16.0 || <=9.14.0", @@ -2011,9 +2013,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.2.tgz", - "integrity": "sha512-Tj+j7Pyzd15wAdSJswvs5CJzJNV+qqSUcr/aCD+jpQSBtXvGnV0pnrjoc8zFTe9fcKCatkpFpOO7yAzpO998HA==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", + "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", "cpu": [ "arm" ], @@ -2024,9 +2026,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.2.tgz", - "integrity": "sha512-xsPeJgh2ThBpUqlLgRfiVYBEf/P1nWlWvReG+aBWfNv3XEBpa6ZCmxSVnxJgLgkNz4IbxpLy64h2gCmAAQLneQ==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", + "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", "cpu": [ "arm64" ], @@ -2037,9 +2039,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.2.tgz", - "integrity": "sha512-KnXU4m9MywuZFedL35Z3PuwiTSn/yqRIhrEA9j+7OSkji39NzVkgxuxTYg5F8ryGysq4iFADaU5osSizMXhU2A==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", + "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", "cpu": [ "arm64" ], @@ -2050,9 +2052,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.2.tgz", - "integrity": "sha512-Hj77A3yTvUeCIx/Vi+4d4IbYhyTwtHj07lVzUgpUq9YpJSEiGJj4vXMKwzJ3w5zp5v3PFvpJNgc/J31smZey6g==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", + "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", "cpu": [ "x64" ], @@ -2063,9 +2065,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.2.tgz", - "integrity": "sha512-RjgKf5C3xbn8gxvCm5VgKZ4nn0pRAIe90J0/fdHUsgztd3+Zesb2lm2+r6uX4prV2eUByuxJNdt647/1KPRq5g==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", + "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", "cpu": [ "arm64" ], @@ -2076,9 +2078,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.2.tgz", - "integrity": "sha512-duq21FoXwQtuws+V9H6UZ+eCBc7fxSpMK1GQINKn3fAyd9DFYKPJNcUhdIKOrMFjLEJgQskoMoiuizMt+dl20g==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", + "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", "cpu": [ "x64" ], @@ -2089,9 +2091,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.2.tgz", - "integrity": "sha512-6npqOKEPRZkLrMcvyC/32OzJ2srdPzCylJjiTJT2c0bwwSGm7nz2F9mNQ1WrAqCBZROcQn91Fno+khFhVijmFA==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", + "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", "cpu": [ "arm" ], @@ -2102,9 +2104,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.2.tgz", - "integrity": "sha512-V9Xg6eXtgBtHq2jnuQwM/jr2mwe2EycnopO8cbOvpzFuySCGtKlPCI3Hj9xup/pJK5Q0388qfZZy2DqV2J8ftw==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", + "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", "cpu": [ "arm" ], @@ -2115,9 +2117,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.2.tgz", - "integrity": "sha512-uCFX9gtZJoQl2xDTpRdseYuNqyKkuMDtH6zSrBTA28yTfKyjN9hQ2B04N5ynR8ILCoSDOrG/Eg+J2TtJ1e/CSA==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", + "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", "cpu": [ "arm64" ], @@ -2128,9 +2130,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.2.tgz", - "integrity": "sha512-/PU9P+7Rkz8JFYDHIi+xzHabOu9qEWR07L5nWLIUsvserrxegZExKCi2jhMZRd0ATdboKylu/K5yAXbp7fYFvA==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", + "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", "cpu": [ "arm64" ], @@ -2141,9 +2143,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.2.tgz", - "integrity": "sha512-eCHmol/dT5odMYi/N0R0HC8V8QE40rEpkyje/ZAXJYNNoSfrObOvG/Mn+s1F/FJyB7co7UQZZf6FuWnN6a7f4g==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", + "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", "cpu": [ "ppc64" ], @@ -2154,9 +2156,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.2.tgz", - "integrity": "sha512-DEP3Njr9/ADDln3kNi76PXonLMSSMiCir0VHXxmGSHxCxDfQ70oWjHcJGfiBugzaqmYdTC7Y+8Int6qbnxPBIQ==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", + "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", "cpu": [ "riscv64" ], @@ -2167,9 +2169,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.2.tgz", - "integrity": "sha512-NHGo5i6IE/PtEPh5m0yw5OmPMpesFnzMIS/lzvN5vknnC1sXM5Z/id5VgcNPgpD+wHmIcuYYgW+Q53v+9s96lQ==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", + "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", "cpu": [ "s390x" ], @@ -2180,9 +2182,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.2.tgz", - "integrity": "sha512-PaW2DY5Tan+IFvNJGHDmUrORadbe/Ceh8tQxi8cmdQVCCYsLoQo2cuaSj+AU+YRX8M4ivS2vJ9UGaxfuNN7gmg==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", + "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", "cpu": [ "x64" ], @@ -2193,9 +2195,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.2.tgz", - "integrity": "sha512-dOlWEMg2gI91Qx5I/HYqOD6iqlJspxLcS4Zlg3vjk1srE67z5T2Uz91yg/qA8sY0XcwQrFzWWiZhMNERylLrpQ==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", + "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", "cpu": [ "x64" ], @@ -2206,9 +2208,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.2.tgz", - "integrity": "sha512-euMIv/4x5Y2/ImlbGl88mwKNXDsvzbWUlT7DFky76z2keajCtcbAsN9LUdmk31hAoVmJJYSThgdA0EsPeTr1+w==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", + "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", "cpu": [ "arm64" ], @@ -2219,9 +2221,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.2.tgz", - "integrity": "sha512-RsnE6LQkUHlkC10RKngtHNLxb7scFykEbEwOFDjr3CeCMG+Rr+cKqlkKc2/wJ1u4u990urRHCbjz31x84PBrSQ==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", + "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", "cpu": [ "ia32" ], @@ -2232,9 +2234,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.2.tgz", - "integrity": "sha512-foJM5vv+z2KQmn7emYdDLyTbkoO5bkHZE1oth2tWbQNGW7mX32d46Hz6T0MqXdWS2vBZhaEtHqdy9WYwGfiliA==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", + "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", "cpu": [ "x64" ], @@ -2342,6 +2344,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bootstrap": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-3.4.0.tgz", + "integrity": "sha512-LS05hVAAsX86qbHg7W+ydwBlNHrVCoFw6wEP3/uW4eYmRXl08bWmPeN/+onM+8qZTFfDgUlG/OItJI8SW972oQ==", + "dev": true, + "dependencies": { + "@types/jquery": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2401,6 +2412,15 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jquery": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", + "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -2445,6 +2465,12 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/sizzle": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -2473,16 +2499,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", - "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", + "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/type-utils": "8.14.0", - "@typescript-eslint/utils": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/type-utils": "8.15.0", + "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2506,15 +2532,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", - "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", + "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4" }, "engines": { @@ -2534,13 +2560,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", - "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", + "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0" + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2551,13 +2577,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", - "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", + "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/utils": "8.15.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2568,6 +2594,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -2575,9 +2604,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", + "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2588,13 +2617,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", - "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", + "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2616,15 +2645,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", - "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", + "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0" + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2635,16 +2664,21 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", - "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", + "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.15.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2654,6 +2688,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -6855,9 +6901,9 @@ } }, "node_modules/rollup": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.2.tgz", - "integrity": "sha512-KreA+PzWmk2yaFmZVwe6GB2uBD86nXl86OsDkt1bJS9p3vqWuEQ6HnJJ+j/mZi/q0920P99/MVRlB4L3crpF5w==", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", + "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", "dev": true, "dependencies": { "@types/estree": "1.0.6" @@ -6870,24 +6916,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.2", - "@rollup/rollup-android-arm64": "4.27.2", - "@rollup/rollup-darwin-arm64": "4.27.2", - "@rollup/rollup-darwin-x64": "4.27.2", - "@rollup/rollup-freebsd-arm64": "4.27.2", - "@rollup/rollup-freebsd-x64": "4.27.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.2", - "@rollup/rollup-linux-arm-musleabihf": "4.27.2", - "@rollup/rollup-linux-arm64-gnu": "4.27.2", - "@rollup/rollup-linux-arm64-musl": "4.27.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.2", - "@rollup/rollup-linux-riscv64-gnu": "4.27.2", - "@rollup/rollup-linux-s390x-gnu": "4.27.2", - "@rollup/rollup-linux-x64-gnu": "4.27.2", - "@rollup/rollup-linux-x64-musl": "4.27.2", - "@rollup/rollup-win32-arm64-msvc": "4.27.2", - "@rollup/rollup-win32-ia32-msvc": "4.27.2", - "@rollup/rollup-win32-x64-msvc": "4.27.2", + "@rollup/rollup-android-arm-eabi": "4.27.3", + "@rollup/rollup-android-arm64": "4.27.3", + "@rollup/rollup-darwin-arm64": "4.27.3", + "@rollup/rollup-darwin-x64": "4.27.3", + "@rollup/rollup-freebsd-arm64": "4.27.3", + "@rollup/rollup-freebsd-x64": "4.27.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", + "@rollup/rollup-linux-arm-musleabihf": "4.27.3", + "@rollup/rollup-linux-arm64-gnu": "4.27.3", + "@rollup/rollup-linux-arm64-musl": "4.27.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", + "@rollup/rollup-linux-riscv64-gnu": "4.27.3", + "@rollup/rollup-linux-s390x-gnu": "4.27.3", + "@rollup/rollup-linux-x64-gnu": "4.27.3", + "@rollup/rollup-linux-x64-musl": "4.27.3", + "@rollup/rollup-win32-arm64-msvc": "4.27.3", + "@rollup/rollup-win32-ia32-msvc": "4.27.3", + "@rollup/rollup-win32-x64-msvc": "4.27.3", "fsevents": "~2.3.2" } }, @@ -7758,14 +7804,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.14.0.tgz", - "integrity": "sha512-K8fBJHxVL3kxMmwByvz8hNdBJ8a0YqKzKDX6jRlrjMuNXyd5T2V02HIq37+OiWXvUUOXgOOGiSSOh26Mh8pC3w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.15.0.tgz", + "integrity": "sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.14.0", - "@typescript-eslint/parser": "8.14.0", - "@typescript-eslint/utils": "8.14.0" + "@typescript-eslint/eslint-plugin": "8.15.0", + "@typescript-eslint/parser": "8.15.0", + "@typescript-eslint/utils": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7774,6 +7820,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true diff --git a/_dev/package.json b/_dev/package.json index 5c36df58e..e1f48532a 100644 --- a/_dev/package.json +++ b/_dev/package.json @@ -27,7 +27,9 @@ "devDependencies": { "@eslint/js": "^9.9.1", "@prestashopcorp/stylelint-config": "^1.0.0", + "@types/bootstrap": "^3.4.0", "@types/jest": "^29.5.13", + "@types/jquery": "^3.5.32", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "eslint": ">=9.16.0 || <=9.14.0", diff --git a/_dev/src/scss/components/_modal.scss b/_dev/src/scss/components/_modal.scss index 5f376a328..5ed1fcb87 100644 --- a/_dev/src/scss/components/_modal.scss +++ b/_dev/src/scss/components/_modal.scss @@ -57,6 +57,12 @@ $e: ".modal"; } // Custom modals + &__spacer { + display: flex; + flex-direction: column; + gap: 1.5rem; + } + &__no-backup { margin-block: 0; diff --git a/_dev/src/ts/autoUpgrade.ts b/_dev/src/ts/autoUpgrade.ts index 5b4bfe3e4..e01691043 100644 --- a/_dev/src/ts/autoUpgrade.ts +++ b/_dev/src/ts/autoUpgrade.ts @@ -1,8 +1,10 @@ +import ModalContainer from './components/ModalContainer'; import RouteHandler from './routing/RouteHandler'; import ScriptHandler from './routing/ScriptHandler'; export const routeHandler = new RouteHandler(); +export const modalContainer = new ModalContainer(); export const scriptHandler = new ScriptHandler(); -export default { routeHandler, scriptHandler }; +export default { routeHandler, scriptHandler, modalContainer }; diff --git a/_dev/src/ts/components/ModalContainer.ts b/_dev/src/ts/components/ModalContainer.ts new file mode 100644 index 000000000..f66a1d6fd --- /dev/null +++ b/_dev/src/ts/components/ModalContainer.ts @@ -0,0 +1,58 @@ +import DomLifecycle from '../types/DomLifecycle'; +import Hydration from '../utils/Hydration'; + +export default class ModalContainer implements DomLifecycle { + public static readonly cancelEvent = 'cancel'; + public static readonly okEvent = 'ok'; + + public static readonly containerId = 'ua_modal'; + + public mount(): void { + this.modalContainer.addEventListener(Hydration.hydrationEventName, this.#displayModal); + this.modalContainer.addEventListener('click', this.#onClick); + this.modalContainer.addEventListener(ModalContainer.cancelEvent, this.#closeModal); + this.modalContainer.addEventListener(ModalContainer.okEvent, this.#closeModal); + } + + public beforeDestroy(): void { + this.modalContainer.removeEventListener(Hydration.hydrationEventName, this.#displayModal); + this.modalContainer.removeEventListener('click', this.#onClick); + this.modalContainer.removeEventListener(ModalContainer.cancelEvent, this.#closeModal); + this.modalContainer.removeEventListener(ModalContainer.okEvent, this.#closeModal); + } + + public get modalContainer(): HTMLElement { + const container = document.getElementById(ModalContainer.containerId); + + if (!container) { + throw new Error('Cannot find modal container to initialize.'); + } + return container; + } + + #displayModal(): void { + $( + document.getElementById(ModalContainer.containerId)?.getElementsByClassName('modal') || [] + ).modal('show'); + } + + #onClick(ev: Event): void { + const target = ev.target ? (ev.target as HTMLElement) : null; + const modal = target?.closest('.modal'); + + if (modal) { + if (target?.closest("[data-dismiss='modal']")) { + modal.dispatchEvent(new Event(ModalContainer.cancelEvent, { bubbles: true })); + } else if (target?.closest(".modal-footer button:not([data-dismiss='modal'])")) { + modal.dispatchEvent(new Event(ModalContainer.okEvent, { bubbles: true })); + } + } + } + + #closeModal(ev: Event): void { + const modal = ev.target; + if (modal) { + $(modal).modal('hide'); + } + } +} diff --git a/_dev/src/ts/modals/StartUpdateModal.ts b/_dev/src/ts/modals/StartUpdateModal.ts new file mode 100644 index 000000000..a622fea85 --- /dev/null +++ b/_dev/src/ts/modals/StartUpdateModal.ts @@ -0,0 +1,72 @@ +import DomLifecycle from '../types/DomLifecycle'; +import api from '../api/RequestHandler'; + +export default class StartUpdateModal implements DomLifecycle { + protected readonly formId = 'form-confirm-update'; + protected readonly confirmCheckboxId = 'modal-start-update-own-backup'; + + public mount(): void { + this.#form.addEventListener('submit', this.#onSubmit); + this.#form.addEventListener('change', this.#onChange); + + this.#updateSubmitButtonStatus( + document.getElementById('modal-start-update-own-backup') as HTMLInputElement | undefined + ); + } + + public beforeDestroy(): void { + this.#form.removeEventListener('submit', this.#onSubmit); + this.#form.removeEventListener('change', this.#onChange); + } + + get #form(): HTMLFormElement { + const form = document.forms.namedItem('form-confirm-update'); + if (!form) { + throw new Error('Form not found'); + } + + // We implement the same way to check from the other scripts, even though there is only one value. + // This will ease any potential refacto. + ['routeToSubmit'].forEach((data) => { + if (!form.dataset[data]) { + throw new Error(`Missing data ${data} from form dataset.`); + } + }); + + return form; + } + + get #submitButton(): HTMLButtonElement { + const submitButton = Array.from(this.#form.elements).find( + (element) => element instanceof HTMLButtonElement && element.type === 'submit' + ) as HTMLButtonElement | null; + + if (!submitButton) { + throw new Error(`No submit button found for form ${this.#form.id}`); + } + + return submitButton; + } + + readonly #onChange = async (ev: Event) => { + const optionInput = ev.target as HTMLInputElement; + + if (optionInput.id === this.confirmCheckboxId) { + this.#updateSubmitButtonStatus(optionInput); + } + }; + + readonly #onSubmit = async (event: Event) => { + event.preventDefault(); + + await api.post(this.#form.dataset.routeToSubmit!, new FormData(this.#form)); + }; + + #updateSubmitButtonStatus(input?: HTMLInputElement): void { + if (!input || input.checked) { + this.#submitButton.removeAttribute('disabled'); + } else { + this.#submitButton.setAttribute('disabled', 'true'); + } + } +} diff --git a/_dev/src/ts/pages/HomePage.ts b/_dev/src/ts/pages/HomePage.ts index 1430e68b9..1b1476bfe 100644 --- a/_dev/src/ts/pages/HomePage.ts +++ b/_dev/src/ts/pages/HomePage.ts @@ -1,5 +1,5 @@ -import PageAbstract from './PageAbstract'; import api from '../api/RequestHandler'; +import PageAbstract from './PageAbstract'; export default class HomePage extends PageAbstract { constructor() { diff --git a/_dev/src/ts/pages/PageAbstract.ts b/_dev/src/ts/pages/PageAbstract.ts index 546e64a6b..8247b4b15 100644 --- a/_dev/src/ts/pages/PageAbstract.ts +++ b/_dev/src/ts/pages/PageAbstract.ts @@ -1,8 +1,10 @@ +import DomLifecycle from '../types/DomLifecycle'; + /** * @abstract * @description Base abstract class defining the structure for page components, requiring implementation of lifecycle methods for mounting and destruction. */ -export default abstract class PageAbstract { +export default abstract class PageAbstract implements DomLifecycle { /** * @abstract * @description Method to initialize and mount the page component. Should be implemented by subclasses to set up event listeners, render content, etc. diff --git a/_dev/src/ts/pages/UpdatePage.ts b/_dev/src/ts/pages/UpdatePage.ts index 11b62dbbd..1f02ee25e 100644 --- a/_dev/src/ts/pages/UpdatePage.ts +++ b/_dev/src/ts/pages/UpdatePage.ts @@ -1,5 +1,5 @@ -import PageAbstract from './PageAbstract'; import Stepper from '../utils/Stepper'; +import PageAbstract from './PageAbstract'; export default class UpdatePage extends PageAbstract { protected stepCode = 'version-choice'; diff --git a/_dev/src/ts/pages/UpdatePageBackup.ts b/_dev/src/ts/pages/UpdatePageBackup.ts index 7e0ee20c6..0acc29956 100644 --- a/_dev/src/ts/pages/UpdatePageBackup.ts +++ b/_dev/src/ts/pages/UpdatePageBackup.ts @@ -1,13 +1,74 @@ +import api from '../api/RequestHandler'; +import { modalContainer } from '../autoUpgrade'; +import ModalContainer from '../components/ModalContainer'; import UpdatePage from './UpdatePage'; export default class UpdatePageBackup extends UpdatePage { protected stepCode = 'backup'; - constructor() { - super(); - } - public mount() { this.initStepper(); + this.#form.addEventListener('submit', this.#onFormSubmit); + this.#form.addEventListener('change', this.#onInputChange); + + document.getElementById('ua_container')?.addEventListener('click', this.#onClick); + modalContainer.modalContainer.addEventListener(ModalContainer.okEvent, this.#onModalOk); + } + + public beforeDestroy(): void { + this.#form.removeEventListener('submit', this.#onFormSubmit); + this.#form.removeEventListener('change', this.#onInputChange); + + document.getElementById('ua_container')?.removeEventListener('click', this.#onClick); + modalContainer.modalContainer.removeEventListener(ModalContainer.okEvent, this.#onModalOk); + } + + get #form(): HTMLFormElement { + const form = document.forms.namedItem('update-backup-page-form'); + if (!form) { + throw new Error('Form not found'); + } + + ['routeToSave', 'routeToSubmitBackup', 'routeToSubmitUpdate', 'routeToConfirmBackup'].forEach( + (data) => { + if (!form.dataset[data]) { + throw new Error(`Missing data ${data} from form dataset.`); + } + } + ); + + return form; } + + readonly #onClick = async (ev: Event) => { + if ((ev.target as HTMLElement).id === 'update-backup-page-skip-btn') { + const formData = new FormData(); + // TODO: Value currently hardcoded until management of backups is implemented + formData.append('backupDone', JSON.stringify(false)); + await api.post(this.#form.dataset.routeToSubmitUpdate!, formData); + } + }; + + readonly #onModalOk = async (ev: Event) => { + // We handle the backup confirmation modal as it is really basic + if ((ev.target as HTMLElement).id === 'modal-confirm-backup') { + api.post(this.#form.dataset.routeToConfirmBackup!); + } + // The update confirmation modal gets its logic in a dedicated script + }; + + readonly #onInputChange = async (ev: Event) => { + const optionInput = ev.target as HTMLInputElement; + + const data = new FormData(this.#form); + optionInput.setAttribute('disabled', 'true'); + await api.post(this.#form.dataset.routeToSave!, data); + optionInput.removeAttribute('disabled'); + }; + + readonly #onFormSubmit = async (event: Event) => { + event.preventDefault(); + + await api.post(this.#form.dataset.routeToSubmitBackup!, new FormData(this.#form)); + }; } diff --git a/_dev/src/ts/pages/UpdatePageUpdateOptions.ts b/_dev/src/ts/pages/UpdatePageUpdateOptions.ts index da646580f..cbb4d79a0 100644 --- a/_dev/src/ts/pages/UpdatePageUpdateOptions.ts +++ b/_dev/src/ts/pages/UpdatePageUpdateOptions.ts @@ -6,20 +6,20 @@ export default class UpdatePageUpdateOptions extends UpdatePage { public mount() { this.initStepper(); - this.form.addEventListener('submit', this.onSubmit); - this.form.addEventListener('change', this.onChange); + this.#form.addEventListener('submit', this.#onSubmit); + this.#form.addEventListener('change', this.#onChange); } public beforeDestroy() { try { - this.form.removeEventListener('submit', this.onSubmit); - this.form.removeEventListener('change', this.onChange); + this.#form.removeEventListener('submit', this.#onSubmit); + this.#form.removeEventListener('change', this.#onChange); } catch { // Do Nothing, page is likely removed from the DOM already } } - private get form(): HTMLFormElement { + get #form(): HTMLFormElement { const form = document.forms.namedItem('update-options-page-form'); if (!form) { throw new Error('Form not found'); @@ -34,18 +34,18 @@ export default class UpdatePageUpdateOptions extends UpdatePage { return form; } - private readonly onChange = async (ev: Event) => { + readonly #onChange = async (ev: Event) => { const optionInput = ev.target as HTMLInputElement; - const data = new FormData(this.form); + const data = new FormData(this.#form); optionInput.setAttribute('disabled', 'true'); - await api.post(this.form.dataset.routeToSave!, data); + await api.post(this.#form.dataset.routeToSave!, data); optionInput.removeAttribute('disabled'); }; - private readonly onSubmit = async (event: Event) => { + readonly #onSubmit = async (event: Event) => { event.preventDefault(); - await api.post(this.form.dataset.routeToSubmit!, new FormData(this.form)); + await api.post(this.#form.dataset.routeToSubmit!, new FormData(this.#form)); }; } diff --git a/_dev/src/ts/routing/ScriptHandler.ts b/_dev/src/ts/routing/ScriptHandler.ts index 2b3255012..f78ce0cbe 100644 --- a/_dev/src/ts/routing/ScriptHandler.ts +++ b/_dev/src/ts/routing/ScriptHandler.ts @@ -4,12 +4,13 @@ import UpdatePageUpdateOptions from '../pages/UpdatePageUpdateOptions'; import UpdatePageBackup from '../pages/UpdatePageBackup'; import UpdatePageUpdate from '../pages/UpdatePageUpdate'; import UpdatePagePostUpdate from '../pages/UpdatePagePostUpdate'; -import PageAbstract from '../pages/PageAbstract'; +import DomLifecycle from '../types/DomLifecycle'; import { RoutesMatching } from '../types/scriptHandlerTypes'; import { routeHandler } from '../autoUpgrade'; +import StartUpdateModal from '../modals/StartUpdateModal'; export default class ScriptHandler { - #currentScript: PageAbstract | undefined; + #currentScript: DomLifecycle | undefined; /** * @private @@ -22,7 +23,9 @@ export default class ScriptHandler { 'update-page-update-options': UpdatePageUpdateOptions, 'update-page-backup': UpdatePageBackup, 'update-page-update': UpdatePageUpdate, - 'update-page-post-update': UpdatePagePostUpdate + 'update-page-post-update': UpdatePagePostUpdate, + + 'start-update-modal': StartUpdateModal }; /** @@ -33,27 +36,27 @@ export default class ScriptHandler { const currentRoute = routeHandler.getCurrentRoute(); if (currentRoute) { - this.#loadScript(currentRoute); + this.loadScript(currentRoute); } } /** - * @private + * @public * @param {string} routeName - The name of the route to load his associated script. * @returns void * @description Loads and mounts the page script associated with the specified route name. */ - #loadScript(routeName: string) { - const pageClass = this.#routesMatching[routeName]; - if (pageClass) { + loadScript(scriptID: string) { + const classScript = this.#routesMatching[scriptID]; + if (classScript) { try { - this.#currentScript = new pageClass(); + this.#currentScript = new classScript(); this.#currentScript.mount(); } catch (error) { - console.error(`Failed to load script for route ${routeName}:`, error); + console.error(`Failed to load script with ID ${scriptID}:`, error); } } else { - console.debug(`No matching page Class found for route: ${routeName}`); + console.debug(`No matching class found for ID: ${scriptID}`); } } @@ -66,7 +69,7 @@ export default class ScriptHandler { */ public updateRouteScript(newRoute: string) { this.#currentScript?.beforeDestroy(); - this.#loadScript(newRoute); + this.loadScript(newRoute); } /** diff --git a/_dev/src/ts/types/DomLifecycle.ts b/_dev/src/ts/types/DomLifecycle.ts new file mode 100644 index 000000000..656ba9575 --- /dev/null +++ b/_dev/src/ts/types/DomLifecycle.ts @@ -0,0 +1,21 @@ +export interface Destroyable { + /** + * @description Method to clean up and perform necessary teardown operations before the page component is destroyed. Should be implemented by subclasses to remove event listeners, clear timers, etc. + * @returns {void} + */ + beforeDestroy(): void; +} + +export interface Mountable { + /** + * @description Method to initialize and mount the page component. Should be implemented by subclasses to set up event listeners, render content, etc. + * @returns {void} + */ + mount(): void; +} + +/** + * @interface + * @description Base abstract class defining the structure for page components, requiring implementation of lifecycle methods for mounting and destruction. + */ +export default interface DomLifecycle extends Destroyable, Mountable {} diff --git a/_dev/src/ts/types/apiTypes.ts b/_dev/src/ts/types/apiTypes.ts index 96ba564b3..660859d15 100644 --- a/_dev/src/ts/types/apiTypes.ts +++ b/_dev/src/ts/types/apiTypes.ts @@ -2,6 +2,7 @@ interface ApiResponseHydration { hydration: boolean; new_content: string; new_route?: string; + add_script?: string; parent_to_update: string; } diff --git a/_dev/src/ts/utils/Hydration.ts b/_dev/src/ts/utils/Hydration.ts index 14327f811..33b448e3b 100644 --- a/_dev/src/ts/utils/Hydration.ts +++ b/_dev/src/ts/utils/Hydration.ts @@ -1,5 +1,5 @@ import { ApiResponseHydration } from '../types/apiTypes'; -import { routeHandler, scriptHandler } from '../autoUpgrade'; +import { modalContainer, routeHandler, scriptHandler } from '../autoUpgrade'; export default class Hydration { /** @@ -17,6 +17,10 @@ export default class Hydration { */ public hydrationEvent: Event = new Event(Hydration.hydrationEventName); + public constructor() { + modalContainer.mount(); + } + /** * @public * @param {ApiResponseHydration} data - The data containing new content and routing information. @@ -29,11 +33,13 @@ export default class Hydration { if (elementToUpdate && data.new_content) { if (data.new_route) { scriptHandler.unloadRouteScript(); + modalContainer.beforeDestroy(); } elementToUpdate.innerHTML = data.new_content; if (data.new_route) { + modalContainer.mount(); scriptHandler.updateRouteScript(data.new_route); if (!fromPopState) { @@ -41,6 +47,10 @@ export default class Hydration { } } + if (data.add_script) { + scriptHandler.loadScript(data.add_script); + } + elementToUpdate.dispatchEvent(this.hydrationEvent); } } diff --git a/_dev/tests/routing/ScriptHandler.test.ts b/_dev/tests/routing/ScriptHandler.test.ts index 480d5be91..e9b2ebb6a 100644 --- a/_dev/tests/routing/ScriptHandler.test.ts +++ b/_dev/tests/routing/ScriptHandler.test.ts @@ -72,9 +72,7 @@ describe('ScriptHandler', () => { scriptHandler = new ScriptHandler(); - expect(consoleDebugSpy).toHaveBeenCalledWith( - `No matching page Class found for route: ${route}` - ); + expect(consoleDebugSpy).toHaveBeenCalledWith(`No matching class found for ID: ${route}`); }); it('should catch and log errors if page instantiation or mount fails', () => { @@ -92,7 +90,7 @@ describe('ScriptHandler', () => { scriptHandler.updateRouteScript(route); expect(consoleErrorSpy).toHaveBeenCalledWith( - `Failed to load script for route ${route}:`, + `Failed to load script with ID ${route}:`, expect.any(Error) ); }); diff --git a/_dev/tests/utils/Hydration.test.ts b/_dev/tests/utils/Hydration.test.ts index bb41563c3..6ceec4f79 100644 --- a/_dev/tests/utils/Hydration.test.ts +++ b/_dev/tests/utils/Hydration.test.ts @@ -2,10 +2,21 @@ import Hydration from '../../src/ts/utils/Hydration'; import { ApiResponseHydration } from '../../src/ts/types/apiTypes'; import RouteHandler from '../../src/ts/routing/RouteHandler'; import ScriptHandler from '../../src/ts/routing/ScriptHandler'; +import { modalContainer } from '../../src/ts/autoUpgrade'; const setNewRouteMock = jest.spyOn(RouteHandler.prototype, 'setNewRoute'); const updateRouteScriptMock = jest.spyOn(ScriptHandler.prototype, 'updateRouteScript'); const unloadRouteScriptMock = jest.spyOn(ScriptHandler.prototype, 'unloadRouteScript'); +const loadScriptMock = jest.spyOn(ScriptHandler.prototype, 'loadScript'); + +jest.mock('../../src/ts/components/ModalContainer', () => { + return jest.fn().mockImplementation(() => { + return { + mount: jest.fn(), + beforeDestroy: jest.fn() + }; + }); +}); jest.mock('../../src/ts/pages/HomePage', () => { return jest.fn().mockImplementation(() => { @@ -73,6 +84,20 @@ describe('Hydration', () => { expect(updateRouteScriptMock).toHaveBeenCalledWith('new_route_value'); }); + it('should call scriptHandler.loadScript when add_script is provided', () => { + const response: ApiResponseHydration = { + hydration: true, + new_content: `
New Content
`, + parent_to_update: 'parent', + add_script: 'additional_script' + }; + + hydration.hydrate(response); + + expect(updateRouteScriptMock).not.toHaveBeenCalled(); + expect(loadScriptMock).toHaveBeenCalledWith('additional_script'); + }); + it('should call routeHandler.setNewRoute when new_route is provided and fromPopState is false', () => { const response: ApiResponseHydration = { hydration: true, @@ -133,6 +158,34 @@ describe('Hydration', () => { }) ); }); + + it('should refresh the modal container if a new page is loaded', () => { + const response: ApiResponseHydration = { + hydration: true, + new_content: `New Content
`, + parent_to_update: 'parent', + new_route: 'new_route_value' + }; + + hydration.hydrate(response); + + expect(modalContainer.beforeDestroy).toHaveBeenCalledTimes(1); + // Called on Init + refresh + expect(modalContainer.mount).toHaveBeenCalledTimes(2); + }); + + it('should not refresh the modal container if the DOM is untouched', () => { + const response: ApiResponseHydration = { + hydration: true, + new_content: `New Content
`, + parent_to_update: 'non_existent_id' + }; + + hydration.hydrate(response); + + // Called on Init + expect(modalContainer.mount).toHaveBeenCalledTimes(1); + }); }); describe('Hydration and scripts lifecycle', () => { diff --git a/_dev/tsconfig.json b/_dev/tsconfig.json index 252f8bc30..1bb686364 100644 --- a/_dev/tsconfig.json +++ b/_dev/tsconfig.json @@ -19,7 +19,7 @@ "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, - "types": ["jest", "node"], + "types": ["jest", "node", "jquery", "bootstrap"], }, "include": [ "src/ts/**/*.ts", diff --git a/classes/AjaxResponseBuilder.php b/classes/AjaxResponseBuilder.php index 73849ac3b..4ed749fd0 100644 --- a/classes/AjaxResponseBuilder.php +++ b/classes/AjaxResponseBuilder.php @@ -6,7 +6,10 @@ class AjaxResponseBuilder { - public static function hydrationResponse(string $parentToUpdate, string $newContent, ?string $newRoute = null): JsonResponse + /** + * @param array{newRoute?:string, addScript?:string} $options + */ + public static function hydrationResponse(string $parentToUpdate, string $newContent, ?array $options = []): JsonResponse { $arrayToReturn = [ 'hydration' => true, @@ -14,8 +17,12 @@ public static function hydrationResponse(string $parentToUpdate, string $newCont 'new_content' => $newContent, ]; - if ($newRoute) { - $arrayToReturn['new_route'] = $newRoute; + if ($options['newRoute']) { + $arrayToReturn['new_route'] = $options['newRoute']; + } + + if ($options['addScript']) { + $arrayToReturn['add_script'] = $options['addScript']; } return new JsonResponse($arrayToReturn); diff --git a/classes/Router/Router.php b/classes/Router/Router.php index 51e6b456d..1be451291 100644 --- a/classes/Router/Router.php +++ b/classes/Router/Router.php @@ -98,6 +98,26 @@ public function __construct(UpgradeContainer $upgradeContainer) 'controller' => UpdatePageBackupController::class, 'method' => 'step', ], + Routes::UPDATE_STEP_BACKUP_SAVE_OPTION => [ + 'controller' => UpdatePageBackupController::class, + 'method' => 'saveOption', + ], + Routes::UPDATE_STEP_BACKUP_SUBMIT_BACKUP => [ + 'controller' => UpdatePageBackupController::class, + 'method' => 'submitBackup', + ], + Routes::UPDATE_STEP_BACKUP_SUBMIT_UPDATE => [ + 'controller' => UpdatePageBackupController::class, + 'method' => 'submitUpdate', + ], + Routes::UPDATE_STEP_BACKUP_CONFIRM_BACKUP => [ + 'controller' => UpdatePageBackupController::class, + 'method' => 'startBackup', + ], + Routes::UPDATE_STEP_BACKUP_CONFIRM_UPDATE => [ + 'controller' => UpdatePageBackupController::class, + 'method' => 'startUpdate', + ], Routes::UPDATE_PAGE_UPDATE => [ 'controller' => UpdatePageUpdateController::class, 'method' => 'index', diff --git a/classes/Router/Routes.php b/classes/Router/Routes.php index 60740aa45..494d6b02d 100644 --- a/classes/Router/Routes.php +++ b/classes/Router/Routes.php @@ -24,6 +24,11 @@ class Routes /* step: backup */ const UPDATE_PAGE_BACKUP = 'update-page-backup'; const UPDATE_STEP_BACKUP = 'update-step-backup'; + const UPDATE_STEP_BACKUP_SAVE_OPTION = 'update-step-backup-save-option'; + const UPDATE_STEP_BACKUP_SUBMIT_BACKUP = 'update-step-backup-submit-backup'; + const UPDATE_STEP_BACKUP_SUBMIT_UPDATE = 'update-step-backup-submit-update'; + const UPDATE_STEP_BACKUP_CONFIRM_BACKUP = 'update-step-backup-confirm-backup'; + const UPDATE_STEP_BACKUP_CONFIRM_UPDATE = 'update-step-backup-confirm-update'; /* step: update */ const UPDATE_PAGE_UPDATE = 'update-page-update'; diff --git a/classes/Twig/PageSelectors.php b/classes/Twig/PageSelectors.php index 38d659cbc..1c0131ee4 100644 --- a/classes/Twig/PageSelectors.php +++ b/classes/Twig/PageSelectors.php @@ -7,6 +7,7 @@ class PageSelectors public const PAGE_PARENT_ID = 'update_assistant'; public const STEP_PARENT_ID = 'ua_container'; public const STEPPER_PARENT_ID = 'stepper_content'; + public const MODAL_PARENT_ID = 'ua_modal'; public const RADIO_CARD_ONLINE_PARENT_ID = 'radio_card_online'; public const RADIO_CARD_ARCHIVE_PARENT_ID = 'radio_card_archive'; @@ -19,6 +20,7 @@ public static function getAllSelectors(): array 'page_parent_id' => self::PAGE_PARENT_ID, 'step_parent_id' => self::STEP_PARENT_ID, 'stepper_parent_id' => self::STEPPER_PARENT_ID, + 'modal_parent_id' => self::MODAL_PARENT_ID, 'radio_card_online_parent_id' => self::RADIO_CARD_ONLINE_PARENT_ID, 'radio_card_archive_parent_id' => self::RADIO_CARD_ARCHIVE_PARENT_ID, ]; diff --git a/classes/Twig/UpdateSteps.php b/classes/Twig/UpdateSteps.php index 3164668a3..7645e9a18 100644 --- a/classes/Twig/UpdateSteps.php +++ b/classes/Twig/UpdateSteps.php @@ -38,7 +38,7 @@ public function setSteps(): void 'title' => $this->translator->trans('Update options'), ], self::STEP_BACKUP => [ - 'title' => $this->translator->trans('Backup'), + 'title' => $this->translator->trans('Back up your store'), ], self::STEP_UPDATE => [ 'title' => $this->translator->trans('Update'), diff --git a/controllers/admin/self-managed/AbstractPageController.php b/controllers/admin/self-managed/AbstractPageController.php index c4830672c..f6fb6445a 100644 --- a/controllers/admin/self-managed/AbstractPageController.php +++ b/controllers/admin/self-managed/AbstractPageController.php @@ -96,7 +96,7 @@ public function index() $this->getPageTemplate(), $this->getParams() ), - $this->displayRouteInUrl() + ['newRoute' => $this->displayRouteInUrl()] ); } diff --git a/controllers/admin/self-managed/AbstractPageWithStepController.php b/controllers/admin/self-managed/AbstractPageWithStepController.php index d9ea1ea6c..766444281 100644 --- a/controllers/admin/self-managed/AbstractPageWithStepController.php +++ b/controllers/admin/self-managed/AbstractPageWithStepController.php @@ -53,7 +53,7 @@ public function step() '@ModuleAutoUpgrade/steps/' . $this->getStepTemplate() . '.html.twig', $params ), - $this->displayRouteInUrl() + ['newRoute' => $this->displayRouteInUrl()] ); } diff --git a/controllers/admin/self-managed/UpdatePageBackupController.php b/controllers/admin/self-managed/UpdatePageBackupController.php index e9016daa8..0a30e002f 100644 --- a/controllers/admin/self-managed/UpdatePageBackupController.php +++ b/controllers/admin/self-managed/UpdatePageBackupController.php @@ -27,7 +27,14 @@ namespace PrestaShop\Module\AutoUpgrade\Controller; +use PrestaShop\Module\AutoUpgrade\AjaxResponseBuilder; +use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeConfiguration; +use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeFileNames; +use PrestaShop\Module\AutoUpgrade\Router\Routes; +use PrestaShop\Module\AutoUpgrade\Twig\PageSelectors; use PrestaShop\Module\AutoUpgrade\Twig\UpdateSteps; +use PrestaShop\Module\AutoUpgrade\Twig\ValidatorToFormFormater; +use Symfony\Component\HttpFoundation\JsonResponse; class UpdatePageBackupController extends AbstractPageWithStepController { @@ -43,6 +50,64 @@ protected function getStepTemplate(): string return self::CURRENT_STEP; } + protected function displayRouteInUrl(): ?string + { + return Routes::UPDATE_PAGE_BACKUP; + } + + public function submitBackup(): JsonResponse + { + $imagesIncluded = $this->upgradeContainer->getUpgradeConfiguration()->shouldBackupImages(); + + return $this->displayModal($imagesIncluded ? 'modal-backup-all' : 'modal-backup', [ + 'modalId' => 'modal-confirm-backup', + ]); + } + + public function submitUpdate(): JsonResponse + { + return $this->displayModal('modal-update', [ + 'noBackUp' => !$this->request->request->getBoolean('backupDone', false), + 'modalId' => 'modal-confirm-update', + + 'form_route_to_confirm' => Routes::UPDATE_STEP_BACKUP_CONFIRM_UPDATE, + + // TODO: assets_base_path is provided by all controllers. What about a asset() twig function instead? + 'assets_base_path' => $this->upgradeContainer->getAssetsEnvironment()->getAssetsBaseUrl($this->request), + ]); + } + + public function startBackup(): JsonResponse + { + return AjaxResponseBuilder::nextRouteResponse(Routes::UPDATE_STEP_BACKUP); + } + + public function startUpdate(): JsonResponse + { + return AjaxResponseBuilder::nextRouteResponse(Routes::UPDATE_STEP_UPDATE); + } + + public function saveOption(): JsonResponse + { + $upgradeConfiguration = $this->upgradeContainer->getUpgradeConfiguration(); + $upgradeConfigurationStorage = $this->upgradeContainer->getUpgradeConfigurationStorage(); + + $config = [ + UpgradeConfiguration::PS_AUTOUP_KEEP_IMAGES => $this->request->request->getBoolean(UpgradeConfiguration::PS_AUTOUP_KEEP_IMAGES, false), + ]; + + $errors = $this->upgradeContainer->getConfigurationValidator()->validate($config); + if (empty($errors)) { + $upgradeConfiguration->merge($config); + $upgradeConfigurationStorage->save($upgradeConfiguration, UpgradeFileNames::CONFIG_FILENAME); + } + + return $this->getRefreshOfForm(array_merge( + $this->getParams(), + ['errors' => ValidatorToFormFormater::format($errors)] + )); + } + /** * @return array * @@ -50,15 +115,50 @@ protected function getStepTemplate(): string */ protected function getParams(): array { + $upgradeConfiguration = $this->upgradeContainer->getUpgradeConfiguration(); $updateSteps = new UpdateSteps($this->upgradeContainer->getTranslator()); return array_merge( $updateSteps->getStepParams($this::CURRENT_STEP), [ - // TODO - 'default_backup_files_and_database' => true, - 'default_include_images' => false, + 'form_route_to_save' => Routes::UPDATE_STEP_BACKUP_SAVE_OPTION, + 'form_route_to_submit_backup' => Routes::UPDATE_STEP_BACKUP_SUBMIT_BACKUP, + 'form_route_to_submit_update' => Routes::UPDATE_STEP_BACKUP_SUBMIT_UPDATE, + 'form_route_to_confirm_backup' => Routes::UPDATE_STEP_BACKUP_CONFIRM_BACKUP, + + 'form_fields' => [ + 'include_images' => [ + 'field' => UpgradeConfiguration::PS_AUTOUP_KEEP_IMAGES, + 'value' => $upgradeConfiguration->shouldBackupImages(), + ], + ], ] ); } + + private function getRefreshOfForm(array $params): JsonResponse + { + return AjaxResponseBuilder::hydrationResponse( + PageSelectors::STEP_PARENT_ID, + $this->getTwig()->render( + '@ModuleAutoUpgrade/steps/' . $this->getStepTemplate() . '.html.twig', + $params + ), + ['newRoute' => $this->displayRouteInUrl()] + ); + } + + private function displayModal(string $modalName, array $params): JsonResponse + { + $options = $modalName === 'modal-update' ? ['addScript' => 'start-update-modal'] : null; + + return AjaxResponseBuilder::hydrationResponse( + PageSelectors::MODAL_PARENT_ID, + $this->getTwig()->render( + '@ModuleAutoUpgrade/modals/' . $modalName . '.html.twig', + $params + ), + $options + ); + } } diff --git a/controllers/admin/self-managed/UpdatePageUpdateOptionsController.php b/controllers/admin/self-managed/UpdatePageUpdateOptionsController.php index 5e1d991cb..8bdd53199 100644 --- a/controllers/admin/self-managed/UpdatePageUpdateOptionsController.php +++ b/controllers/admin/self-managed/UpdatePageUpdateOptionsController.php @@ -85,7 +85,7 @@ public function saveOption(): JsonResponse public function submit(): JsonResponse { - return AjaxResponseBuilder::nextRouteResponse(Routes::UPDATE_PAGE_BACKUP); + return AjaxResponseBuilder::nextRouteResponse(Routes::UPDATE_STEP_BACKUP); } /** @@ -131,7 +131,7 @@ private function getRefreshOfForm(array $params): JsonResponse '@ModuleAutoUpgrade/steps/' . $this->getStepTemplate() . '.html.twig', $params ), - $this->displayRouteInUrl() + ['newRoute' => $this->displayRouteInUrl()] ); } } diff --git a/storybook/stories/components/Modal.stories.js b/storybook/stories/components/Modal.stories.js index d1706094e..66cb3bcc6 100644 --- a/storybook/stories/components/Modal.stories.js +++ b/storybook/stories/components/Modal.stories.js @@ -45,6 +45,8 @@ export const Default = { modalSize: "lg", psBaseUri: "/", modalDanger: false, + + assets_base_path: "", }, }; diff --git a/storybook/stories/components/ModalBackup.stories.js b/storybook/stories/components/ModalBackup.stories.js index ab1abdf1c..69df828b6 100644 --- a/storybook/stories/components/ModalBackup.stories.js +++ b/storybook/stories/components/ModalBackup.stories.js @@ -34,8 +34,6 @@ export default { export const Backup = { args: { ...Modal.args, - title: "Start backup?", - message: "Your files and database will be backed up.", modalSize: "md", }, }; diff --git a/storybook/stories/components/ModalBackupAll.stories.js b/storybook/stories/components/ModalBackupAll.stories.js index e6f203ff2..2fa04d230 100644 --- a/storybook/stories/components/ModalBackupAll.stories.js +++ b/storybook/stories/components/ModalBackupAll.stories.js @@ -34,8 +34,6 @@ export default { export const BackupWithImg = { args: { ...Modal.args, - title: "Start backup?", - message: "Your files, database, and images will be backed up.", modalSize: "md", }, }; diff --git a/storybook/stories/components/ModalUpdate.stories.js b/storybook/stories/components/ModalUpdate.stories.js index c7e682c26..efa384241 100644 --- a/storybook/stories/components/ModalUpdate.stories.js +++ b/storybook/stories/components/ModalUpdate.stories.js @@ -34,8 +34,6 @@ export default { export const Update = { args: { ...Modal.args, - title: "Start update?", - message: "You are about to launch the update, do you want to continue?", modalSize: "lg", noBackUp: false, }, diff --git a/storybook/stories/pages/Backup.stories.js b/storybook/stories/pages/Backup.stories.js index 228c06ede..aec239e44 100644 --- a/storybook/stories/pages/Backup.stories.js +++ b/storybook/stories/pages/Backup.stories.js @@ -39,8 +39,17 @@ export const Backup = { code: "backup", title: "Backup", }, - default_backup_files_and_database: true, - default_include_images: false, + + form_fields: { + include_images: { + field: 'PS_AUTOUP_KEEP_IMAGES', + value: true, + }, + }, + + form_route_to_save: "update-step-update-options-save-option", + form_route_to_submit: "update-step-update-options-submit-form", + step_parent_id: "ua_container", // Stepper ...Stepper.args, diff --git a/tests/unit/Twig/ValidatorToFormFormaterTest.php b/tests/unit/Twig/ValidatorToFormFormaterTest.php new file mode 100644 index 000000000..62686e06f --- /dev/null +++ b/tests/unit/Twig/ValidatorToFormFormaterTest.php @@ -0,0 +1,102 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ + +namespace PrestaShop\Module\AutoUpgrade\Tests\Twig; + +use PHPUnit\Framework\TestCase; +use PrestaShop\Module\AutoUpgrade\Twig\ValidatorToFormFormater; + +class ValidatorToFormFormaterTest extends TestCase +{ + public function testEmptyArray() + { + $input = []; + $expected = []; + + $actual = ValidatorToFormFormater::format($input); + + $this->assertSame($expected, $actual); + } + + public function testErrorsForSpecificFields() + { + $input = [ + [ + 'message' => 'Oh no', + 'target' => 'oneDisgustingField', + ], + ]; + $expected = [ + 'oneDisgustingField' => 'Oh no', + ]; + + $actual = ValidatorToFormFormater::format($input); + + $this->assertSame($expected, $actual); + } + + public function testGlobalErrors() + { + $input = [ + [ + 'message' => 'Eww', + ], + ]; + $expected = [ + 'global' => 'Eww', + ]; + + $actual = ValidatorToFormFormater::format($input); + + $this->assertSame($expected, $actual); + } + + public function testMixedErrors() + { + $input = [ + [ + 'message' => 'This field cannot be blank', + 'target' => 'theMandatoryField', + ], + [ + 'message' => 'Eww', + ], + [ + 'message' => 'Oh no', + 'target' => 'oneDisgustingField', + ], + ]; + $expected = [ + 'theMandatoryField' => 'This field cannot be blank', + 'global' => 'Eww', + 'oneDisgustingField' => 'Oh no', + ]; + + $actual = ValidatorToFormFormater::format($input); + + $this->assertSame($expected, $actual); + } +} diff --git a/views/templates/layouts/page.html.twig b/views/templates/layouts/page.html.twig index 994933b2a..9e45528bb 100644 --- a/views/templates/layouts/page.html.twig +++ b/views/templates/layouts/page.html.twig @@ -20,4 +20,6 @@ {% endblock %} + + {% endblock %} diff --git a/views/templates/modals/modal-backup-all.html.twig b/views/templates/modals/modal-backup-all.html.twig index ed8d46185..046f826d9 100644 --- a/views/templates/modals/modal-backup-all.html.twig +++ b/views/templates/modals/modal-backup-all.html.twig @@ -1,18 +1,6 @@ -{% extends "@ModuleAutoUpgrade/components/modal.html.twig" %} +{% extends "@ModuleAutoUpgrade/modals/modal-backup.html.twig" %} -{% block modal_extra_content %}{% endblock %} - -{% block modal_footer %} - +{% block modal_content %} + {% set message = 'Your files, database, and images will be backed up.'|trans({}) %} + {{ parent() }} {% endblock %} diff --git a/views/templates/modals/modal-backup.html.twig b/views/templates/modals/modal-backup.html.twig index ed8d46185..d62456500 100644 --- a/views/templates/modals/modal-backup.html.twig +++ b/views/templates/modals/modal-backup.html.twig @@ -1,4 +1,10 @@ {% extends "@ModuleAutoUpgrade/components/modal.html.twig" %} +{% set title = 'Start backup?'|trans({}) %} +{% set message = 'Your files and database will be backed up.'|trans({}) %} + +{% block modal_content %} + {{ parent() }} +{% endblock %} {% block modal_extra_content %}{% endblock %} diff --git a/views/templates/modals/modal-update.html.twig b/views/templates/modals/modal-update.html.twig index 803e09981..92b60aab7 100644 --- a/views/templates/modals/modal-update.html.twig +++ b/views/templates/modals/modal-update.html.twig @@ -1,11 +1,21 @@ {% extends "@ModuleAutoUpgrade/components/modal.html.twig" %} +{% set title = 'Start update?'|trans({}) %} + {% block modal_content %} +