From d92783c14cb128ec02f5af12749a0adaffe8cacb Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Mon, 8 Dec 2025 15:37:54 +0100 Subject: [PATCH 01/16] Update CORE UI version to 2.0.1 ISSUE: LIS-90 --- package-lock.json | 76 +- package.json | 4 +- .../web/assets/fonts/sq-icons/sq-icons.eot | Bin 6218 -> 6196 bytes .../web/assets/fonts/sq-icons/sq-icons.svg | 6 +- .../web/assets/fonts/sq-icons/sq-icons.ttf | Bin 6048 -> 6028 bytes .../web/assets/fonts/sq-icons/sq-icons.woff | Bin 6124 -> 6104 bytes view/adminhtml/web/css/sequra-core.css | 1149 ++++++++++------- view/adminhtml/web/js/AdvancedController.js | 435 +++++++ view/adminhtml/web/js/AjaxService.js | 7 +- .../web/js/ConnectionSettingsForm.js | 26 +- view/adminhtml/web/js/DeploymentsModalForm.js | 3 +- view/adminhtml/web/js/ElementGenerator.js | 103 +- view/adminhtml/web/js/GeneralSettingsForm.js | 213 ++- view/adminhtml/web/js/OnboardingController.js | 11 +- .../web/js/OrderStatusMappingSettingsForm.js | 63 +- view/adminhtml/web/js/PaymentController.js | 22 +- view/adminhtml/web/js/SettingsController.js | 33 +- view/adminhtml/web/js/StateController.js | 139 +- view/adminhtml/web/js/TranslationService.js | 2 +- view/adminhtml/web/js/UtilityService.js | 28 +- view/adminhtml/web/js/ValidationService.js | 38 +- view/adminhtml/web/js/WidgetSettingsForm.js | 12 +- view/adminhtml/web/lang/en.json | 52 +- view/adminhtml/web/lang/es.json | 54 +- view/adminhtml/web/lang/fr.json | 54 +- view/adminhtml/web/lang/it.json | 54 +- view/adminhtml/web/lang/pt.json | 54 +- 27 files changed, 1868 insertions(+), 770 deletions(-) create mode 100644 view/adminhtml/web/js/AdvancedController.js diff --git a/package-lock.json b/package-lock.json index 8e3b7da1..d6cb4ace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,17 +12,17 @@ "@playwright/test": "^1.56.1", "@types/node": "^22.10.7", "playwright-fixture-for-plugins": "github:sequra/playwright-fixture-for-plugins", - "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.0" + "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.1" } }, "node_modules/@playwright/test": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", - "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.1" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -32,15 +32,29 @@ } }, "node_modules/@types/node": { - "version": "22.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz", - "integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==", + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff-dom": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/diff-dom/-/diff-dom-5.2.1.tgz", + "integrity": "sha512-G6WquwIa/XOxiYDYRMxfJj+c+a6LXMv7zgXoAQ3eRjTYkc8Lc4Aq97krTu/B15r243FR7dyeSxDenzKtO6sbfA==", + "dev": true, + "license": "LGPL-3.0" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -56,14 +70,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/json-formatter-js": { + "version": "2.5.23", + "resolved": "https://registry.npmjs.org/json-formatter-js/-/json-formatter-js-2.5.23.tgz", + "integrity": "sha512-Cbm8wHXjo/C56aCePP1VuKvjxoMEmL7g7Ckss1oWFFlCsvOEEbye1kTeaNNaqba1Cl6YpIOYAnK65pUQ8mDIUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/playwright": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", - "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -76,9 +97,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -89,15 +110,30 @@ } }, "node_modules/playwright-fixture-for-plugins": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/sequra/playwright-fixture-for-plugins.git#9e3bd9916f6db6ff6c981e28600baa81c22410d0", + "version": "1.1.0", + "resolved": "git+ssh://git@github.com/sequra/playwright-fixture-for-plugins.git#e226c3b1bac768dae94f0e12b615c4c03468cfd3", "dev": true, "license": "MIT" }, "node_modules/sequra-core-admin-fe": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/sequra/integration-core-ui.git#f853f474d659b7e0edad41cb23f052d124cdbc9b", - "dev": true + "version": "2.0.1", + "resolved": "git+ssh://git@github.com/sequra/integration-core-ui.git#ed85c2c460cd1ff73fc5aebfc06a518248b4a8f1", + "dev": true, + "dependencies": { + "json-formatter-js": "^2.5.23", + "simple-datatables": "^10.0.0" + } + }, + "node_modules/simple-datatables": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/simple-datatables/-/simple-datatables-10.2.0.tgz", + "integrity": "sha512-Lq3k5JZ4WXT0G0dhIAUfYc3M+GRs9y8U15wfbF7i67/rPm8wa43Iyrac+yP+lXQVTy1MPCPvxvZ44P9N4k0OhQ==", + "dev": true, + "license": "LGPL-3.0", + "dependencies": { + "dayjs": "^1.11.10", + "diff-dom": "5.2.1" + } }, "node_modules/undici-types": { "version": "6.21.0", diff --git a/package.json b/package.json index 4ede9502..7a6d0546 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "description": "1. [About seQura](#about-sequra) 2. [Installation guide](https://sequra.atlassian.net/wiki/spaces/DOC/pages/1377304583/MAGENTO+2) 3. [Sign-up](#sign-up) 4. [For developers](#for-developers)", "main": "index.js", - "scripts": { + "scripts": { "copy-assets": "cp -r node_modules/sequra-core-admin-fe/dist/resources/assets/* view/adminhtml/web/assets/", "copy-css": "cp -r node_modules/sequra-core-admin-fe/dist/resources/css/* view/adminhtml/web/css/", "copy-js": "cp -r node_modules/sequra-core-admin-fe/dist/resources/js/* view/adminhtml/web/js/", @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "devDependencies": { - "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.0", + "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.1", "@playwright/test": "^1.56.1", "@types/node": "^22.10.7", "playwright-fixture-for-plugins": "github:sequra/playwright-fixture-for-plugins" diff --git a/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.eot b/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.eot index 0400e864c5edcc2443a63bfd34d606ad61b8e256..223600b90ba2a8fb346932a2ad171eabdbc0ef48 100644 GIT binary patch delta 1274 zcmbtUO=#O@7=Ay?k{sKzT-lOr*8wd~>?O;OZN+ig{5k1HS(|}8uvwwq>ZWdDH%?6A zKuT%cOG}|+Bvom1$ZhPhKnZw<9(Hhd++mbLp~u2{s0L$)G8luzdzC1mD?JSMg1+y0 z-tW=(KJTZWpW{Eo>A?WN*Bf-&-Y!(0ZDfXJ3Gz^cC0K?MEW!dPkbr)OZ*26|dja4_ zye`gE3RUCocN72#Bd^UCma8~>k+0+In4Q0N#aJ!)k#7R{hUbcf%e}ihK>+_#Tpyal z1p5>90zh;i@0_dDRzF98+^lhaesQLtt_|0a-$5R)6jrMcjS?&%KE{NyP^c7xvzZ)n z+=Se#E-u#q?EuL0`XzFjg!S*q$f++dA)qY^Y(PLUD8pfJ#xWz@+HJK=HRiye4X42y z!TlU*{VLmKO18;F`Wx>fdLrQai~vDBk9$C?2|zV*KS-ZIipBq9&SUDR=Tx(?ZmZZ> zlRT!Hx}E-gA8+HrU-SiXM9#CfZA`0a$7=fPwp#@B1?P1fe{2(px4bb?;H+?A3U;4M}cuZLPCN?3%hmZp3A`Ht!I%3h(V0uVR%97}3 z8J4glnI1|F#-fo9mJ$83q{dQ_l$ugSRg8!c>f^mPN_WpdP0MDr+KQIt`4Hho_p;hB zhITOa_I-mnG#G+D7=w#wG4{4U zS+YE$Z#F&jZ;Bs!3%H`7L*{P{SG8_(t`ufMm z&*Y}#0|)(~{qj|TbGtdg=jL$EFigb9xjVa(1B$|p(mdZD<~>dq-{o+KH_PRR56k8D za3VQ1Ijx=fbYenPJGV0VFY`0CtfpmaIG!wWoFH%*=6FZp+!3E72h+!oheA=j3|&D*x6 zFG*wdl35#r0tcM1upRN5g=ug>z`_jXvle#Zb9`WaOZGO*cyZ+b+rR@=3sab%w6GoV zZ41*NLF51nz~I6;YrzSFu%kbb10UpzOUtFj1trlRfB&z`RO$ES2ZX|pIjCm>^glf$ B`xgKJ delta 844 zcmaJ=Ur19?9RAKd=kBj_OWp2V37WD8wXrdAmIflBWg;XkLX@`DT;z1PJs1_*gF-~a zI3xx>41$PyTttu$B0WgpgAYMZu?VXBP(6e%QLW#dS9#6&uod9}XL2CyTfZ%!w(85%vL*J!j& z&s@Kxr(Q&Y*w=Fd-?fVvXpohdS`Tl^j1r47-NO6L|vz*kB4kRF?vn@C32 zx-OD7)(fYT3mF81j5hFYlhHVvOi#@>JWi4RLYq9u%w==nlt5dO2{yn2@(JrclOUsx z+TsvkXw-#n(_MOv?$ZP1-{rC~pIqn`-J!e5t*^MO{81Ac*A$KU{`h`THIq&LS{YMZ zeU%bG0b<9QAu5lG`+u(*ZSeB9{918ENh{W2`1nhnlRw2*>kCv2Ru=MD)bj$9~xpVjM9W48qf{cWvHrNcGli)Hr|po zo!)isg)X^!_TV&D21=7hLkFW=_nvKSBY&8%M2H)62s?z5$qj8FF60Mqfc49E-!bc; ztzhrB7i7YBa&gA|pd2>zm|PIQZE}@5^_$#6{&|x-5Wqv@PYF9Y9!mDPf@)+<&dEPz zazXr#$yHjhWuMo;Lcy3BIM9KjT-5wk7pCU3SLbFMBcVw2;6@k$VO0UI9FMSHNP=++ F{{oijqXPf{ diff --git a/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.svg b/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.svg index 5c89a225..a3e5e9c6 100644 --- a/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.svg +++ b/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.svg @@ -11,7 +11,7 @@ - + @@ -22,8 +22,8 @@ - - + + \ No newline at end of file diff --git a/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.ttf b/view/adminhtml/web/assets/fonts/sq-icons/sq-icons.ttf index 085935324e0987f4e9a11b9dcb936d88c0e41927..7ff96173b0d7c5788f4bf0b18cad9ef1d04a05af 100644 GIT binary patch delta 1227 zcmbtUOK1~O6umE#%;Yznbdt%KG{vE56Rp;ynWky&F9t=_q7x-S#b2nkZES3?5h|jo z3rnFQ+7Vm9g&?j35wq#SMGzORLv*9wa~J_~{f-lpQzN}O`C3m2g?SXB zh1^ULLIf`mR**{`pR|%*QEWb zWlgQTv|CIB9=&!FqptFXO|8FEX>W88$_Y^U#19j;eh@T6AAn80|C*u(%atVveSRHBKDy@F&C)+j( zu{@c{=+yw`HG>&6=!Xs%fHP<@{JTHVYj{N8OrrZwmwiMC|7AQZV`J-eWxX9nM844~ z9wV5*VPBre=O>mAq*9%+K)~N@*>7bi+wzkD0 zlEn5~IIcOsHP{_oo7F1J=JQKS`FwMrGnU#ltnA-6G$hNd50cqa*^yFOQPL$`caE|w z&$Af%Ixn%#pxZ0@5=~8he@Nu{MwiRYH*lQ9yQ9Wur+<3y3VicvuKznYa8wwdzQ*6I z@2Xiiuty)^e?RJ7p@TVRt=e3+2kL8Kpgf`9jX{AGdJSwwe9gcX{Il3(U>fzbf$jJ^ z-ov+u*@lV>Gj(i&1}GYsLVcHk&4}j=YymF>>zD@`6UU5%9lGF|de7S;Mj;EMa1o|q W0;b>sNYDv8AgaFeK2@)an|}dDhVDH8 delta 849 zcma)4O-NKx6#njeZ{Garj84vsjTU2yP~u->nPedm)1Mhw6Cz3uYL4XSq_Z#zoS=fB z7IrBNq6UH%3T5H~KINa~&+;h)8@7`(7HqSu- za3cd1EoaU)@AdlhJycd0L&M2<>gA)A20&DkUmJ_hq_~!mFK~5?Ow^K|p5Lc9DScFMP&YI)e(DxY{G8cGn&RhM_OTtAcQ<)m+^*1Wi= zIlk`RXu9C-+<@CG2^2sg%GK`p+!&qY!ENpT!VzSF$C8F|%ztMiu zk#$C$S-I>#G&f;QSA_$e7S@QbTiC|#DlM#2KWAYVLU?HYJPqiarL+J*W^tj);Qa6hH&a?< zvB&89U4&=OUpkAirvT~@gR@+U%ubii0BAAP3^Me-zTGy9L5zKlekX%)ms_8$RF^S^ zfRhZz(43#2LfyFb5P6EhyfRd+l$LR0-HX1_L^lavC{?C0hRMj!3~e*H!ovI_7EABk zcy*M->`5}R|2WDO2m=QmY}Rl%$L~l^s}pOyZZvQW6FT7d9MO@Twq}ep{-IvVQ4b~h zOMDJr2~m#_AgGry2Q=ycTpja4b{JjU`#obJrsE!Q^;+6f@d(Vh!`1CSz0aEbFI@Q5 zcZzH~8~im7->7?WH~agRZwc68@iN-$;y1o#v=$2ZJOqA^NW|6EqM?bcXKG)y1_&1f zsHN0N65LD#9WVl5|0e48t;kO+T(hLB*baqVkOAl-yuinICb}~N*+DZ+buA+B0ue|$ zJD3?rbjNoJycVIlnaIR5X2#S^Ew076PdA<$yL*yVE1$QjOIBW%qeL3r$Xi1=tWDcf zEEaI&h!mCOytRbl6;vmCb{iYdN6E+ALX_tU1*QhTPmZstfCWaO560j$mY8_EKB~Jl zV%a;D}-X4v1Q$=YFhgBsc z%Z8$|bHV;Ph6fd;<4bw;1q9gX0C@Ue=HAw9TiW5z*em>C*iW=R;jF*j5^lL^NA$6= zx#s?O5b!1#cDWb%6_@+)O4;FZ9^-kJi})RXv47O**aACS-WIfB*ynOD@@p>lfevw( w^B6zjauEjLw*3*^VZYQxnt~!s!$nwxIhcnFU_d|YffTO)O*p*F+VKyEH^Kq*>Hq)$ delta 860 zcmaKqO-K}B7{~wb%^=1Yfgo2ndI)t69?RkfZZoTk(=l}el=l!0wmW`IG z^Sy}#7^qnr@H^iHiUa(WRXN*OaixAJo1O$%hEkUVFCM&X7)|Gd_laV!OK5cL!Wr^5 zfQl}`o5B3G%t(3|FdC_5w;=lYNo|Hq^2kS>Kzl8(WwQBc@@`N(AUFJAuU zk)4A0y*+)|^fWDOR#F^sh_TRkIy*vM8!diUFjez7GdYpV(}~{BZuPP-PqO`oPg8j% zLXe@*@glZjd+mT-Zku+v_^Vi?EKO;{R&Cu5lDo}uS@EN#Sh6Kq%=~R`(P%~&WAG2m z-|Y+mB%pn-bAe9hxc~P`(Evk!FP|&aOIbWLgG@`g!B6$N z6husc+>bIuv5T&k*JyS8sjrW%aIb#RvtC@5BfLa!;!Aqc^PNg(3##8EvDBlI08?>W zf%KzLUfM0GNK&?~&>!+MpF+F+7Y1nC#bwGaqZ;jV!F~Kzu$6xe#ubzxQOJ}fX{LV4 z$N2N|#i%$gt3CA2zu9~$*s%$}m?o%Y8_$R=a)^ZGbi3n z@lhYbL02oJZ@St;T9c?J^V^$UaV(Q`6I8I^&Q^?^gCNPc&TG33zosy_E NFF~fi!{V!!_6PR3q-g*E diff --git a/view/adminhtml/web/css/sequra-core.css b/view/adminhtml/web/css/sequra-core.css index 58eb7aac..4dec1f8b 100644 --- a/view/adminhtml/web/css/sequra-core.css +++ b/view/adminhtml/web/css/sequra-core.css @@ -1,25 +1,27 @@ @charset "UTF-8"; -html, body { +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); +#sequra-page { margin: 0; padding: 0; width: 100%; min-height: 100%; max-height: 100%; - background-color: white; + font-weight: 400; text-size-adjust: 100%; -webkit-text-size-adjust: 100%; -moz-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } - -button, a { +#sequra-page button, +#sequra-page a { cursor: pointer; } - -/* +#sequra-page { + /* description: 'Remove default browser button styles.' -*/ -button { + */ +} +#sequra-page button { background: transparent; border: 0; padding: 0; @@ -28,13 +30,15 @@ button { text-decoration: inherit; text-transform: inherit; } - -a { +#sequra-page a { text-decoration: none; color: inherit; } - -input, textarea, select, button, a { +#sequra-page input, +#sequra-page textarea, +#sequra-page select, +#sequra-page button, +#sequra-page a { font-size: inherit; font-family: inherit; line-height: inherit; @@ -42,71 +46,116 @@ input, textarea, select, button, a { text-decoration: inherit; text-transform: inherit; } - -button, a, ul, li, div, tr, input, textarea { +#sequra-page button, +#sequra-page a, +#sequra-page ul, +#sequra-page li, +#sequra-page div, +#sequra-page tr, +#sequra-page input, +#sequra-page textarea { outline: none; box-sizing: border-box; } -button:focus, button:hover, a:focus, a:hover, ul:focus, ul:hover, li:focus, li:hover, div:focus, div:hover, tr:focus, tr:hover, input:focus, input:hover, textarea:focus, textarea:hover { +#sequra-page button:focus, #sequra-page button:hover, +#sequra-page a:focus, +#sequra-page a:hover, +#sequra-page ul:focus, +#sequra-page ul:hover, +#sequra-page li:focus, +#sequra-page li:hover, +#sequra-page div:focus, +#sequra-page div:hover, +#sequra-page tr:focus, +#sequra-page tr:hover, +#sequra-page input:focus, +#sequra-page input:hover, +#sequra-page textarea:focus, +#sequra-page textarea:hover { outline: none; } - -ul { +#sequra-page ul { list-style-type: none; margin: 0; padding: 0; padding-inline-start: 0; } -ul > li { +#sequra-page ul > li { margin: 0; padding: 0; } - -/* - description: - 'Fix placeholder styles (https://css-tricks.com/almanac/selectors/p/placeholder/).', - '(default placeholder opacity)' -*/ -input::placeholder { +#sequra-page { + /* + description: + 'Fix placeholder styles (https://css-tricks.com/almanac/selectors/p/placeholder/).', + '(default placeholder opacity)' + */ +} +#sequra-page input::placeholder { opacity: 1; } -input::-ms-input-placeholder { +#sequra-page input::-ms-input-placeholder { opacity: 1; } -input:-ms-input-placeholder { +#sequra-page input:-ms-input-placeholder { opacity: 1; } - -div, p, pre, table, -form, fieldset, -main, header, footer, nav, section, -ul, li, ol, dl, dt, dd, -h1, h2, h3, h4, h5, h6, -hr, -article, aside, details, dialog, figcaption, figure { +#sequra-page div, +#sequra-page p, +#sequra-page pre, +#sequra-page table, +#sequra-page form, +#sequra-page fieldset, +#sequra-page main, +#sequra-page header, +#sequra-page footer, +#sequra-page nav, +#sequra-page section, +#sequra-page ul, +#sequra-page li, +#sequra-page ol, +#sequra-page dl, +#sequra-page dt, +#sequra-page dd, +#sequra-page h1, +#sequra-page h2, +#sequra-page h3, +#sequra-page h4, +#sequra-page h5, +#sequra-page h6, +#sequra-page hr, +#sequra-page article, +#sequra-page aside, +#sequra-page details, +#sequra-page dialog, +#sequra-page figcaption, +#sequra-page figure { box-sizing: border-box; } - -h1, h2, h3, h4, h5, h6, p { +#sequra-page h1, +#sequra-page h2, +#sequra-page h3, +#sequra-page h4, +#sequra-page h5, +#sequra-page h6, +#sequra-page p { margin: 0; padding: 0; - font-weight: 400; text-decoration: inherit; text-transform: inherit; } - -p a { +#sequra-page p a { font-weight: normal; } - -button[disabled] { +#sequra-page button[disabled] { pointer-events: none; } -button[disabled] > * { +#sequra-page button[disabled] > * { pointer-events: none; } - -*, *:before, *:after { +#sequra-page *, +#sequra-page *:before, +#sequra-page *:after { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: geometricPrecision; @@ -139,71 +188,90 @@ button[disabled] > * { return: 'new map which is result of the merge', } */ -h2 { +#sequra-page h2 { font-weight: 600; font-size: 24px; line-height: 1.33; margin: 12px 0; } -table { +#sequra-page table { width: 100%; border-collapse: collapse; } -table thead tr th { +#sequra-page table thead tr th { border-bottom: 1px solid #dce0e5; } -table tr:not(:last-of-type) td { +#sequra-page table tr:not(:last-of-type) td { border-bottom: 1px solid #dce0e5; } -table tr th, table tr td { +#sequra-page table tr th, #sequra-page table tr td { text-align: center; padding: 14px; } -table tr th.sqm--left-aligned, table tr td.sqm--left-aligned { +#sequra-page table tr th.sqm--left-aligned, #sequra-page table tr td.sqm--left-aligned { text-align: left; } -table tr th.sqm--blue-text, table tr td.sqm--blue-text { +#sequra-page table tr th.sqm--blue-text, #sequra-page table tr td.sqm--blue-text { color: #0066ff; } -table tr td { +#sequra-page table tr td { font-weight: 400; } -table tr td:last-of-type { +#sequra-page table tr td:last-of-type { white-space: nowrap; } -table tr th { +#sequra-page table tr th { font-weight: 600; } -#sq-page-wrapper { +#sequra-page .sq-support-link { + display: flex; + color: #686565; + position: absolute; + top: calc(100% + 32px); + left: 0; + transition: color 0.2s; +} +#sequra-page .sq-support-link:hover { + color: #000; +} +#sequra-page .sq-support-link::before { + font-family: "sq-icons"; + content: "\e91c"; + line-height: 1; + font-weight: 500; + font-size: 20px; +} + +#sequra-page #sq-page-wrapper { min-height: 100vh; padding: 30px 20px 60px 30px; background: #f3f6f9; } -#sq-page-wrapper .sq-page-content-wrapper { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper { position: relative; width: 100%; } -#sq-page-wrapper .sq-page-content-wrapper .sq-version-header { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-version-header { position: relative; height: 25px; margin-bottom: 40px; } -#sq-page-wrapper .sq-page-content-wrapper .sq-content-row { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content-row { display: flex; column-gap: 32px; } -#sq-page-wrapper .sq-page-content-wrapper .sq-content-row.sqs--no-version { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content-row.sqs--no-version { margin-top: 126px; } @media (max-width: 1023.98px) { - #sq-page-wrapper .sq-page-content-wrapper .sq-content-row { + #sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content-row { flex-direction: column; row-gap: 32px; } } -#sq-page-wrapper .sq-page-content-wrapper .sq-content { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content { width: 100%; padding: 0; position: relative; @@ -212,145 +280,162 @@ table tr th { box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06); border-radius: 6px; } -#sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 48px; } @media (max-width: 767.98px) { - #sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header { + #sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header { flex-direction: column; align-items: flex-start; row-gap: 16px; } } -#sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header .sq-logo { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header .sq-logo { height: 40px; } -#sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header .sq-store-switcher .sq-field-wrapper { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper .sq-content .sqp-content-header .sq-store-switcher .sq-field-wrapper { margin-bottom: 0; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--onboarding { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--onboarding { border-radius: 6px; background: #fff; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06); padding: 24px; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--onboarding .sq-content { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--onboarding .sq-content { max-width: 660px; box-shadow: none; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner { padding: 44px 36px 36px; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading { display: flex; justify-content: space-between; align-items: center; column-gap: 36px; } @media (max-width: 1023.98px) { - #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading { + #sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading { flex-direction: column; align-items: flex-start; row-gap: 8px; } } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqp-page-heading { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqp-page-heading { max-width: 800px; width: 100%; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown { max-width: 344px; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown .sqp-field-title { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown .sqp-field-title { color: #00C2A3; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown .sqp-field-subtitle { - color: #B1AEBA; +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--payments .sq-content-inner .sq-table-heading .sqm--table-dropdown .sqp-field-subtitle { + color: #b1aeba; } -#sq-page-wrapper .sq-page-content-wrapper.sqv--settings .sq-content-inner { +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--settings .sq-content-inner { max-width: 835px; width: 100%; padding: 32px 48px 52px 48px; } +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--advanced .sq-content-inner { + padding: 44px 36px 36px; +} +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--advanced .sq-content-inner .sq-table-heading { + display: flex; + justify-content: space-between; + align-items: center; + column-gap: 36px; +} +#sequra-page #sq-page-wrapper .sq-page-content-wrapper.sqv--advanced .sq-content-inner .sq-table-heading .sqp-page-heading { + max-width: 800px; + width: 100%; +} @media (max-width: 767.98px) { - #sq-page-wrapper { + #sequra-page #sq-page-wrapper { padding: 0; } } -.sqv--connect .sq-info-button { +.wp-admin #sequra-page #sq-page-wrapper { + background: transparent; +} + +#sequra-page .sqv--connect .sq-info-button { color: #00C2A3; } -.sq-link-button { +#sequra-page .sq-link-button { color: #00C2A3; text-decoration: underline; } -.sq-link-button:hover { +#sequra-page .sq-link-button:hover { color: #00C2A3; } -body { +#sequra-page { color: #000; - background-color: #f3f6f9; } -body > * { +#sequra-page > * { font-family: "Inter", sans-serif; font-size: 15px; line-height: 1.4; } -table { +#sequra-page table { width: 100%; border-collapse: collapse; } -table thead tr th { +#sequra-page table thead tr th { border-bottom: 1px solid #dce0e5; } -table tr:not(:last-of-type) td { +#sequra-page table tr:not(:last-of-type) td { border-bottom: 1px solid #dce0e5; } -table tr th, table tr td { +#sequra-page table tr th, #sequra-page table tr td { text-align: center; padding: 14px; } -table tr th.sqm--left-aligned, table tr td.sqm--left-aligned { +#sequra-page table tr th.sqm--left-aligned, #sequra-page table tr td.sqm--left-aligned { text-align: left; } -table tr th.sqm--blue-text, table tr td.sqm--blue-text { +#sequra-page table tr th.sqm--blue-text, #sequra-page table tr td.sqm--blue-text { color: #0066ff; } -table tr td { +#sequra-page table tr td { font-weight: 400; } -table tr td:last-of-type { +#sequra-page table tr td:last-of-type { white-space: nowrap; } -table tr th { +#sequra-page table tr th { font-weight: 600; } -@font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 400; - src: local("Inter Regular"), url(../assets/fonts/Inter/Inter-Regular.woff2) format("woff2"), url(../assets/fonts/Inter/Inter-Regular.woff) format("woff"); +#sequra-page .sq-support-link { + display: flex; + color: #686565; + position: absolute; + top: calc(100% + 32px); + left: 0; + transition: color 0.2s; } -@font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 600; - src: local("Inter SemiBold"), url(../assets/fonts/Inter/Inter-SemiBold.woff2) format("woff2"), url(../assets/fonts/Inter/Inter-SemiBold.woff) format("woff"); +#sequra-page .sq-support-link:hover { + color: #000; } -@font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 700; - src: local("Inter Bold"), url(../assets/fonts/Inter/Inter-Bold.woff2) format("woff2"), url(../assets/fonts/Inter/Inter-Bold.woff) format("woff"); +#sequra-page .sq-support-link::before { + font-family: "sq-icons"; + content: "\e91c"; + line-height: 1; + font-weight: 500; + font-size: 20px; } + /** * Font face declaration for icons */ @@ -361,7 +446,7 @@ table tr th { font-weight: normal; font-style: normal; } -.sq-button { +#sequra-page .sq-button { font-weight: 600; position: relative; display: inline-block; @@ -372,92 +457,83 @@ table tr th { border: 1px solid; border-radius: 4px; } -.sq-button:focus { +#sequra-page .sq-button:focus { outline: none; } -.sq-button.sqm--small { +#sequra-page .sq-button.sqm--small { padding: 8px; } -.sq-button.sqm--medium { +#sequra-page .sq-button.sqm--medium { padding: 10px 16px; } -.sq-button.sqt--primary { +#sequra-page .sq-button.sqt--primary { background-color: #00C2A3; border-color: #00C2A3; color: #fff; } -.sq-button.sqt--primary:hover, .sq-button.sqt--primary:focus { +#sequra-page .sq-button.sqt--primary:hover, #sequra-page .sq-button.sqt--primary:focus { background-color: #fff; color: #00C2A3; } -.sq-button.sqt--secondary { +#sequra-page .sq-button.sqt--secondary { border: 1px solid #00C2A3; background-color: #fff; color: #00C2A3; } -.sq-button.sqt--secondary:hover { +#sequra-page .sq-button.sqt--secondary:hover { background-color: #00C2A3; color: #fff; } -.sq-button.sqt--cancel { +#sequra-page .sq-button.sqt--cancel { border-color: #dce0e5; color: #394962; } -.sq-button.sqt--cancel:hover { +#sequra-page .sq-button.sqt--cancel:hover { background-color: #dce0e5; } -.sq-button.sqt--danger { +#sequra-page .sq-button.sqt--danger { color: #fff; background-color: #c00e0e; border-color: #c00e0e; } -.sq-button.sqt--danger:hover, .sq-button.sqt--danger:focus { +#sequra-page .sq-button.sqt--danger:hover, #sequra-page .sq-button.sqt--danger:focus { background-color: rgb(215.7669902913, 15.7330097087, 15.7330097087); border-color: rgb(215.7669902913, 15.7330097087, 15.7330097087); } -.sq-button.sqs--disabled, .sq-button:disabled { +#sequra-page .sq-button.sqs--disabled, #sequra-page .sq-button:disabled { background-color: #dce0e5; border-color: transparent; color: #a5afbd; pointer-events: none; } -.sq-store-switcher .sq-field-wrapper { +#sequra-page .sq-store-switcher .sq-field-wrapper { display: flex; column-gap: 16px; align-items: center; } -.sq-store-switcher .sq-field-wrapper.sqm--label-left { +#sequra-page .sq-store-switcher .sq-field-wrapper.sqm--label-left { justify-content: unset; } -.sq-store-switcher .sq-field-wrapper.sqm--label-left .sqp-field-component { +#sequra-page .sq-store-switcher .sq-field-wrapper.sqm--label-left .sqp-field-component { min-width: 115px; column-gap: 16px; } -.sq-store-switcher .sq-field-wrapper .sqp-field-title { +#sequra-page .sq-store-switcher .sq-field-wrapper .sqp-field-title { font-weight: 400; color: #394962; } -.sq-store-switcher .sq-field-wrapper .sqp-dropdown-button .sqs--selected { +#sequra-page .sq-store-switcher .sq-field-wrapper .sqp-dropdown-button .sqs--selected { color: #69778c; } -.sq-store-switcher .sqp-store-switcher-dropdown .sqp-dropdown-list { +#sequra-page .sq-store-switcher .sqp-store-switcher-dropdown .sqp-dropdown-list { z-index: 12; } -.sq-heading-wrapper { - display: flex; -} - -.sq-heading-wrapper .sqm--deployment { - margin-left: auto; - width: auto; -} - -.sq-page-header { +#sequra-page .sq-page-header { margin-bottom: 16px; } -.sq-page-header .sqp-header-top { +#sequra-page .sq-page-header .sqp-header-top { display: flex; justify-content: space-between; align-items: center; @@ -468,81 +544,81 @@ table tr th { box-shadow: 0 6px 6px rgba(0, 0, 0, 0.06); } @media (max-width: 600px) { - .sq-page-header .sqp-header-top { + #sequra-page .sq-page-header .sqp-header-top { flex-direction: column; align-items: flex-start; row-gap: 20px; } - .sq-page-header .sqp-header-top .sqp-menu-items { + #sequra-page .sq-page-header .sqp-header-top .sqp-menu-items { width: 100%; } } -.sq-page-header .sqp-header-bottom { +#sequra-page .sq-page-header .sqp-header-bottom { display: flex; justify-content: space-between; align-items: center; } @media (max-width: 767.98px) { - .sq-page-header .sqp-header-bottom { + #sequra-page .sq-page-header .sqp-header-bottom { padding: 0 15px; } } @media (max-width: 499.98px) { - .sq-page-header .sqp-header-bottom { + #sequra-page .sq-page-header .sqp-header-bottom { flex-direction: column; align-items: flex-start; row-gap: 8px; } } -.sq-page-header .sqp-header-bottom .sq-store-switcher .sq-field-wrapper { +#sequra-page .sq-page-header .sqp-header-bottom .sq-store-switcher .sq-field-wrapper { margin: 0; } -.sq-page-header .sqp-header-bottom .sqp-header-merchant { +#sequra-page .sq-page-header .sqp-header-bottom .sqp-header-merchant { display: flex; align-items: center; column-gap: 16px; } -.sq-page-header .sqp-header-bottom .sqp-header-merchant .sqp-merchant { +#sequra-page .sq-page-header .sqp-header-bottom .sqp-header-merchant .sqp-merchant { display: flex; column-gap: 4px; font-size: 14px; color: #394962; } -.sq-page-header .sqp-header-bottom .sqp-header-merchant .sqp-merchant .sqp-merchant-label { +#sequra-page .sq-page-header .sqp-header-bottom .sqp-header-merchant .sqp-merchant .sqp-merchant-label { font-weight: 700; color: #394962; } -.sq-page-header .sqp-page-header { +#sequra-page .sq-page-header .sqp-page-header { display: flex; align-items: center; flex-wrap: wrap; column-gap: 10px; row-gap: 10px; } -.sq-page-header .sqp-header-logo { +#sequra-page .sq-page-header .sqp-header-logo { display: flex; align-items: center; column-gap: 10px; } -.sq-page-header .sqp-header-logo .sq-logo { +#sequra-page .sq-page-header .sqp-header-logo .sq-logo { height: 28px; } -.sq-page-header .sqp-download-version { +#sequra-page .sq-page-header .sqp-download-version { font-weight: 400; display: flex; column-gap: 4px; padding: 3px 8px; font-size: 13px; - color: #EA9D56; + color: #ea9d56; border-radius: 15px; - border: 1px solid #B65700; + border: 1px solid #b65700; transition: all 0.2s; } -.sq-page-header .sqp-download-version:hover, .sq-page-header .sqp-download-version:focus { +#sequra-page .sq-page-header .sqp-download-version:hover, #sequra-page .sq-page-header .sqp-download-version:focus { color: #fff; - background: #EA9D56; + background: #ea9d56; } -.sq-page-header .sqp-menu-items { +#sequra-page .sq-page-header .sqp-menu-items { display: flex; align-items: center; column-gap: 1px; @@ -550,10 +626,8 @@ table tr th { border-radius: 50px; overflow: hidden; background-color: #f4f4f4; - max-width: 300px; - width: 100%; } -.sq-page-header .sqp-menu-items a { +#sequra-page .sq-page-header .sqp-menu-items a { display: flex; justify-content: center; flex: 1; @@ -561,51 +635,52 @@ table tr th { font-size: 14px; padding: 8px; transition: all 0.2s; + width: 150px; } -.sq-page-header .sqp-menu-items a:hover, .sq-page-header .sqp-menu-items a:focus { +#sequra-page .sq-page-header .sqp-menu-items a:hover, #sequra-page .sq-page-header .sqp-menu-items a:focus { color: #ffffff; background: #1c1c1c; text-decoration: none; } -.sq-page-header .sqp-menu-items a.sqs--active { +#sequra-page .sq-page-header .sqp-menu-items a.sqs--active { color: #ffffff; background: #1c1c1c; pointer-events: none; } -.sq-page-header .sq-mode-badge { +#sequra-page .sq-page-header .sq-mode-badge { padding: 4px 10px; border-radius: 6px; text-transform: capitalize; font-size: 13px; } -.sq-page-header .sq-mode-badge.sqt--live { - color: #0ABF53; +#sequra-page .sq-page-header .sq-mode-badge.sqt--live { + color: #0abf53; background: rgba(10, 191, 83, 0.12); } -.sq-page-header .sq-mode-badge.sqt--test { - color: #B65700; +#sequra-page .sq-page-header .sq-mode-badge.sqt--test { + color: #b65700; background: rgba(234, 157, 86, 0.12); } -.sqp-page-heading { +#sequra-page .sqp-page-heading { margin-bottom: 21px; } -.sqp-page-heading .sqp-page-title { +#sequra-page .sqp-page-heading .sqp-page-title { font-weight: 600; margin-bottom: 12px; font-size: 24px; color: #000; } -.sqp-page-heading .sqp-page-description { +#sequra-page .sqp-page-heading .sqp-page-description { font-size: 16px; color: #000; } -.sq-single-select-dropdown { +#sequra-page .sq-single-select-dropdown { position: relative; border-radius: 6px; } -.sq-single-select-dropdown .sqp-dropdown-button { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-button { width: 100%; padding: 8px 16px; display: flex; @@ -615,7 +690,7 @@ table tr th { border: 1px solid #dce0e5; background-color: #fff; } -.sq-single-select-dropdown .sqp-dropdown-button:after { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-button:after { font-family: "sq-icons"; content: "\e90d"; line-height: 1; @@ -623,17 +698,17 @@ table tr th { font-size: 11px; color: #8390a3; } -.sq-single-select-dropdown .sqp-dropdown-button span { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-button span { font-weight: 400; display: flex; width: 100%; justify-content: space-between; color: #69778c; } -.sq-single-select-dropdown .sqp-dropdown-button span.sqs--selected { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-button span.sqs--selected { color: #00112c; } -.sq-single-select-dropdown .sqp-dropdown-button input, .sq-single-select-dropdown .sqp-dropdown-button .sq-text-input { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-button input, #sequra-page .sq-single-select-dropdown .sqp-dropdown-button .sq-text-input { display: none; position: absolute; top: 0; @@ -644,7 +719,7 @@ table tr th { height: 100%; border-radius: 6px 6px 0 0; } -.sq-single-select-dropdown .sqp-dropdown-list { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list { display: none; position: absolute; width: 100%; @@ -660,13 +735,13 @@ table tr th { color: #00112c; z-index: 11; } -.sq-single-select-dropdown .sqp-dropdown-list.sqs--show { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list.sqs--show { display: flex; } -.sq-single-select-dropdown .sqp-dropdown-list::-webkit-scrollbar { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list::-webkit-scrollbar { display: none; } -.sq-single-select-dropdown .sqp-dropdown-list-item { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item { position: relative; display: flex; align-items: center; @@ -677,22 +752,22 @@ table tr th { color: #00112c; cursor: pointer; } -.sq-single-select-dropdown .sqp-dropdown-list-item:hover { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item:hover { background-color: #f3f6f9; } -.sq-single-select-dropdown .sqp-dropdown-list-item.sqv--no-items { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item.sqv--no-items { cursor: default; font-style: italic; font-size: 13px; } -.sq-single-select-dropdown .sqp-dropdown-list-item.sqv--no-items:hover { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item.sqv--no-items:hover { background: none; } -.sq-single-select-dropdown .sqp-dropdown-list-item.sqs--selected { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item.sqs--selected { position: relative; padding-right: 32px; } -.sq-single-select-dropdown .sqp-dropdown-list-item.sqs--selected:after { +#sequra-page .sq-single-select-dropdown .sqp-dropdown-list-item.sqs--selected:after { font-family: "sq-icons"; content: "\e91a"; line-height: 1; @@ -703,47 +778,36 @@ table tr th { color: #394962; font-weight: 700; } -.sq-single-select-dropdown.sqs--active .sqp-dropdown-button { +#sequra-page .sq-single-select-dropdown.sqs--active .sqp-dropdown-button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border: 1px solid; border-color: #6FCF97 #6FCF97 #e3e6eb; } -.sq-single-select-dropdown.sqs--active .sqp-dropdown-button.sqs--search-active input { +#sequra-page .sq-single-select-dropdown.sqs--active .sqp-dropdown-button.sqs--search-active input { border: 1px solid; border-color: #6FCF97 #6FCF97 #e3e6eb; display: block; } -.sq-single-select-dropdown.sqs--active .sqp-dropdown-list { +#sequra-page .sq-single-select-dropdown.sqs--active .sqp-dropdown-list { border: 1px solid #6FCF97; border-top: none; } -.sq-single-select-dropdown.sqs--disabled .sqp-dropdown-button { +#sequra-page .sq-single-select-dropdown.sqs--disabled .sqp-dropdown-button { background-color: #f3f6f9; color: #69778c; border: 1px solid #dce0e5; pointer-events: none; } -.sq-single-select-dropdown.sqs--error .sqp-dropdown-button { +#sequra-page .sq-single-select-dropdown.sqs--error .sqp-dropdown-button { border: 1px solid #ee5959; } -.sq-multi-item-selector { +#sequra-page .sq-multi-item-selector { position: relative; border-radius: 6px; - display: flex; - justify-content: flex-start; - align-items: center; - flex-wrap: wrap; - column-gap: 8px; - row-gap: 8px; - padding: 8px 16px; - background: #fff; - border: 1px solid #dce0e5; - cursor: pointer; - min-height: 40px; } -.sq-multi-item-selector .sqp-dropdown-button { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button { width: 100%; padding: 8px 16px; display: flex; @@ -753,7 +817,7 @@ table tr th { border: 1px solid #dce0e5; background-color: #fff; } -.sq-multi-item-selector .sqp-dropdown-button:after { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button:after { font-family: "sq-icons"; content: "\e90d"; line-height: 1; @@ -761,17 +825,17 @@ table tr th { font-size: 11px; color: #8390a3; } -.sq-multi-item-selector .sqp-dropdown-button span { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button span { font-weight: 400; display: flex; width: 100%; justify-content: space-between; color: #69778c; } -.sq-multi-item-selector .sqp-dropdown-button span.sqs--selected { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button span.sqs--selected { color: #00112c; } -.sq-multi-item-selector .sqp-dropdown-button input, .sq-multi-item-selector .sqp-dropdown-button .sq-text-input { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button input, #sequra-page .sq-multi-item-selector .sqp-dropdown-button .sq-text-input { display: none; position: absolute; top: 0; @@ -782,7 +846,7 @@ table tr th { height: 100%; border-radius: 6px 6px 0 0; } -.sq-multi-item-selector .sqp-dropdown-list { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list { display: none; position: absolute; width: 100%; @@ -798,13 +862,13 @@ table tr th { color: #00112c; z-index: 11; } -.sq-multi-item-selector .sqp-dropdown-list.sqs--show { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list.sqs--show { display: flex; } -.sq-multi-item-selector .sqp-dropdown-list::-webkit-scrollbar { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list::-webkit-scrollbar { display: none; } -.sq-multi-item-selector .sqp-dropdown-list-item { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item { position: relative; display: flex; align-items: center; @@ -815,22 +879,22 @@ table tr th { color: #00112c; cursor: pointer; } -.sq-multi-item-selector .sqp-dropdown-list-item:hover { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item:hover { background-color: #f3f6f9; } -.sq-multi-item-selector .sqp-dropdown-list-item.sqv--no-items { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item.sqv--no-items { cursor: default; font-style: italic; font-size: 13px; } -.sq-multi-item-selector .sqp-dropdown-list-item.sqv--no-items:hover { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item.sqv--no-items:hover { background: none; } -.sq-multi-item-selector .sqp-dropdown-list-item.sqs--selected { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item.sqs--selected { position: relative; padding-right: 32px; } -.sq-multi-item-selector .sqp-dropdown-list-item.sqs--selected:after { +#sequra-page .sq-multi-item-selector .sqp-dropdown-list-item.sqs--selected:after { font-family: "sq-icons"; content: "\e91a"; line-height: 1; @@ -841,46 +905,59 @@ table tr th { color: #394962; font-weight: 700; } -.sq-multi-item-selector.sqs--active .sqp-dropdown-button { +#sequra-page .sq-multi-item-selector.sqs--active .sqp-dropdown-button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border: 1px solid; border-color: #6FCF97 #6FCF97 #e3e6eb; } -.sq-multi-item-selector.sqs--active .sqp-dropdown-button.sqs--search-active input { +#sequra-page .sq-multi-item-selector.sqs--active .sqp-dropdown-button.sqs--search-active input { border: 1px solid; border-color: #6FCF97 #6FCF97 #e3e6eb; display: block; } -.sq-multi-item-selector.sqs--active .sqp-dropdown-list { +#sequra-page .sq-multi-item-selector.sqs--active .sqp-dropdown-list { border: 1px solid #6FCF97; border-top: none; } -.sq-multi-item-selector.sqs--disabled .sqp-dropdown-button { +#sequra-page .sq-multi-item-selector.sqs--disabled .sqp-dropdown-button { background-color: #f3f6f9; color: #69778c; border: 1px solid #dce0e5; pointer-events: none; } -.sq-multi-item-selector.sqs--error .sqp-dropdown-button { +#sequra-page .sq-multi-item-selector.sqs--error .sqp-dropdown-button { border: 1px solid #ee5959; } -.sq-multi-item-selector .sq-single-select-dropdown { +#sequra-page .sq-multi-item-selector { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: wrap; + column-gap: 8px; + row-gap: 8px; + padding: 8px 16px; + background: #fff; + border: 1px solid #dce0e5; + cursor: pointer; + min-height: 40px; +} +#sequra-page .sq-multi-item-selector .sq-single-select-dropdown { display: none; position: unset; flex-grow: 1; min-width: 50%; } -.sq-multi-item-selector .sq-single-select-dropdown .sq-text-input { +#sequra-page .sq-multi-item-selector .sq-single-select-dropdown .sq-text-input { padding: 0; height: 100%; } -.sq-multi-item-selector .sq-multi-input { +#sequra-page .sq-multi-item-selector .sq-multi-input { flex: 1 0 auto; border: none; padding: 0; } -.sq-multi-item-selector .sqp-dropdown-button { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button { position: relative; display: flex; justify-content: space-between; @@ -893,38 +970,38 @@ table tr th { border: none; background-color: #fff; } -.sq-multi-item-selector .sqp-dropdown-button:after { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button:after { display: none; } -.sq-multi-item-selector .sqp-dropdown-button .sq-text-input { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button .sq-text-input { width: 100%; border: none; height: 100%; } -.sq-multi-item-selector .sqp-dropdown-button .sq-text-input:focus { +#sequra-page .sq-multi-item-selector .sqp-dropdown-button .sq-text-input:focus { border: none; box-shadow: none; } -.sq-multi-item-selector.sqs--active { +#sequra-page .sq-multi-item-selector.sqs--active { border-radius: 6px 6px 0 0; border-color: #00C2A3 #00C2A3 #dce0e5; } -.sq-multi-item-selector.sqs--active .sq-single-select-dropdown { +#sequra-page .sq-multi-item-selector.sqs--active .sq-single-select-dropdown { display: inline-block; } -.sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-button.sqs--search-active { +#sequra-page .sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-button.sqs--search-active { border: none; } -.sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-button.sqs--search-active input { +#sequra-page .sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-button.sqs--search-active input { border: none; } -.sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-list { +#sequra-page .sq-multi-item-selector.sqs--active .sq-single-select-dropdown .sqp-dropdown-list { width: calc(100% + 2px); left: -1px; top: calc(100% + 1px); border-color: #dce0e5 #00C2A3 #00C2A3; } -.sq-multi-item-selector .sqp-selected-item { +#sequra-page .sq-multi-item-selector .sqp-selected-item { position: relative; padding: 3px 24px 3px 6px; font-size: 11px; @@ -932,7 +1009,7 @@ table tr th { border: 1px solid #00C2A3; border-radius: 4px; } -.sq-multi-item-selector .sqp-selected-item .sqp-remove-button { +#sequra-page .sq-multi-item-selector .sqp-selected-item .sqp-remove-button { position: absolute; width: 16px; height: 16px; @@ -941,19 +1018,19 @@ table tr th { padding: 2px; line-height: 0; } -.sq-multi-item-selector .sqp-selected-item .sqp-remove-button:before { +#sequra-page .sq-multi-item-selector .sqp-selected-item .sqp-remove-button:before { font-family: "sq-icons"; content: "\e90a"; line-height: 1; font-weight: 500; font-size: 6px; } -.sq-multi-item-selector .sqp-selected-item .sqp-remove-button:hover { +#sequra-page .sq-multi-item-selector .sqp-selected-item .sqp-remove-button:hover { background-color: #00C2A3; color: #fff; } -.sq-text-input { +#sequra-page .sq-text-input { width: 100%; padding: 8px 16px; font-size: 14px; @@ -961,78 +1038,80 @@ table tr th { border: 1px solid #dce0e5; border-radius: 6px; } -.sq-text-input:focus, .sq-text-input:active { +#sequra-page .sq-text-input:focus, #sequra-page .sq-text-input:active { border-color: #00C2A3; } -.sq-text-input.sqm--full-width { +#sequra-page .sq-text-input.sqm--full-width { width: 100%; } -.sq-text-input::placeholder { +#sequra-page .sq-text-input::placeholder { color: #69778c; } -.sq-text-input::-ms-input-placeholder { +#sequra-page .sq-text-input::-ms-input-placeholder { color: #69778c; } -.sq-text-input:-ms-input-placeholder { +#sequra-page .sq-text-input:-ms-input-placeholder { color: #69778c; } -.sq-text-area { +#sequra-page .sq-text-area { resize: vertical; } -.sq-password { +#sequra-page .sq-password { position: relative; display: flex; flex-direction: row; align-items: center; } -.sq-password input { +#sequra-page .sq-password input { width: 100%; padding: 8px 16px; font-size: 14px; color: #000; border: 1px solid #dce0e5; border-radius: 6px; - padding-right: 41px; - width: 100%; } -.sq-password input:focus, .sq-password input:active { +#sequra-page .sq-password input:focus, #sequra-page .sq-password input:active { border-color: #00C2A3; } -.sq-password input.sqm--full-width { +#sequra-page .sq-password input.sqm--full-width { width: 100%; } -.sq-password input::placeholder { +#sequra-page .sq-password input::placeholder { color: #69778c; } -.sq-password input::-ms-input-placeholder { +#sequra-page .sq-password input::-ms-input-placeholder { color: #69778c; } -.sq-password input:-ms-input-placeholder { +#sequra-page .sq-password input:-ms-input-placeholder { color: #69778c; } -.sq-password span { +#sequra-page .sq-password input { + padding-right: 41px; + width: 100%; +} +#sequra-page .sq-password span { cursor: pointer; position: absolute; right: 15px; height: 18px; } -.sq-password span:before { +#sequra-page .sq-password span:before { font-family: "sq-icons"; content: "\e911"; line-height: 1; font-weight: 500; font-size: 16px; } -.sq-password span:hover:before { +#sequra-page .sq-password span:hover:before { font-weight: 600; } -.sq-radio-input [type=radio] { +#sequra-page .sq-radio-input [type=radio] { display: none; } -.sq-radio-input [type=radio] + span { +#sequra-page .sq-radio-input [type=radio] + span { position: relative; padding-left: 28px; cursor: pointer; @@ -1040,7 +1119,7 @@ table tr th { display: inline-block; color: #555; } -.sq-radio-input [type=radio] + span:before { +#sequra-page .sq-radio-input [type=radio] + span:before { content: ""; position: absolute; left: 0; @@ -1050,8 +1129,9 @@ table tr th { border: 1px solid #ddd; border-radius: 100%; background: #fff; + box-sizing: border-box; } -.sq-radio-input [type=radio] + span:after { +#sequra-page .sq-radio-input [type=radio] + span:after { content: ""; width: 15px; height: 15px; @@ -1063,26 +1143,27 @@ table tr th { transition: all 0.2s ease; opacity: 0; transform: scale(0); + box-sizing: border-box; } -.sq-radio-input [type=radio]:checked + span:after { +#sequra-page .sq-radio-input [type=radio]:checked + span:after { opacity: 1; transform: scale(1); } -.sq-radio-input-group { +#sequra-page .sq-radio-input-group { display: flex; align-items: center; gap: 20px; } -.sq-loader { +#sequra-page .sq-loader { width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; } -.sq-loader .sqp-spinner { +#sequra-page .sq-loader .sqp-spinner { border: 3px solid rgba(0, 194, 163, 0.1); border-top-color: #00C2A3; border-radius: 50%; @@ -1090,17 +1171,17 @@ table tr th { height: 24px; animation: loader-spin 0.8s linear infinite; } -.sq-loader.sqt--small .sqp-spinner { +#sequra-page .sq-loader.sqt--small .sqp-spinner { border-width: 2px; width: 16px; height: 16px; } -.sq-loader.sqt--large .sqp-spinner { +#sequra-page .sq-loader.sqt--large .sqp-spinner { border-width: 4px; width: 48px; height: 48px; } -.sq-loader.sqm--dark .sqp-spinner { +#sequra-page .sq-loader.sqm--dark .sqp-spinner { border-color: rgba(255, 255, 255, 0.1); border-top-color: #fff; } @@ -1113,18 +1194,18 @@ table tr th { } } -.sq-toggle { +#sequra-page .sq-toggle { position: relative; display: inline-block; width: 34px; height: 17px; } -.sq-toggle input { +#sequra-page .sq-toggle input { opacity: 0; width: 0; height: 0; } -.sq-toggle .sqp-toggle-round { +#sequra-page .sq-toggle .sqp-toggle-round { position: absolute; cursor: pointer; top: 0; @@ -1135,7 +1216,7 @@ table tr th { transition: 0.4s; border-radius: 13px; } -.sq-toggle .sqp-toggle-round:before { +#sequra-page .sq-toggle .sqp-toggle-round:before { position: absolute; content: ""; height: 13px; @@ -1146,36 +1227,40 @@ table tr th { transition: 0.4s; border-radius: 50%; } -.sq-toggle input:checked + .sqp-toggle-round { +#sequra-page .sq-toggle input:checked + .sqp-toggle-round { background-color: #6FCF97; } -.sq-toggle input:focus + .sqp-toggle-round { +#sequra-page .sq-toggle input:focus + .sqp-toggle-round { box-shadow: 0 0 1px #6FCF97; } -.sq-toggle input:checked + .sqp-toggle-round:before { +#sequra-page .sq-toggle input:checked + .sqp-toggle-round:before { transform: translateX(17px); } +#sequra-page .sq-toggle input:disabled + .sqp-toggle-round { + opacity: 0.35; + cursor: not-allowed; +} -.sq-checkbox { +#sequra-page .sq-checkbox { display: flex; justify-content: center; width: 24px; height: 24px; } -.sq-checkbox input { +#sequra-page .sq-checkbox input { opacity: 0; width: 0; height: 0; display: none; } -.sq-checkbox .sqp-checkmark { +#sequra-page .sq-checkbox .sqp-checkmark { cursor: pointer; width: 14px; height: 14px; border: 1px solid #a5afbd; border-radius: 4px; } -.sq-checkbox .sqp-checkmark:before { +#sequra-page .sq-checkbox .sqp-checkmark:before { height: 16px; width: 16px; display: flex; @@ -1184,125 +1269,119 @@ table tr th { transition: 0.4s; color: white; } -.sq-checkbox input:checked + .sqp-checkmark { +#sequra-page .sq-checkbox input:checked + .sqp-checkmark { background-color: #6FCF97; border: none; width: 16px; height: 16px; } -.sq-checkbox input:focus + .sqp-checkmark { +#sequra-page .sq-checkbox input:focus + .sqp-checkmark { box-shadow: 0 0 1px #6FCF97; } -.sq-checkbox input:checked + .sqp-checkmark:before { +#sequra-page .sq-checkbox input:checked + .sqp-checkmark:before { content: "✓"; } -.sq-data-frame { - border: 1px solid #dce0e5; - padding: 20px; - border-radius: 6px; -} - -.sq-field-wrapper { +#sequra-page .sq-field-wrapper { width: 100%; display: flex; flex-direction: column; margin: 0 0 32px; } -.sq-field-wrapper .sqp-field-title { +#sequra-page .sq-field-wrapper .sqp-field-title { font-size: 15px; } -.sq-field-wrapper .sqp-field-subtitle { +#sequra-page .sq-field-wrapper .sqp-field-subtitle { font-size: 13px; } -.sq-field-wrapper .sqp-field-title { +#sequra-page .sq-field-wrapper .sqp-field-title { font-weight: 600; margin-bottom: 4px; color: #00112c; } -.sq-field-wrapper .sqp-field-title:not(:last-child) { +#sequra-page .sq-field-wrapper .sqp-field-title:not(:last-child) { margin-bottom: 0; } -.sq-field-wrapper .sqp-field-subtitle { +#sequra-page .sq-field-wrapper .sqp-field-subtitle { display: block; margin-bottom: 4px; color: #20304c; } -.sq-field-wrapper .sqp-field-component:invalid { +#sequra-page .sq-field-wrapper .sqp-field-component:invalid { border: 1px solid #e50000; color: #e50000; } -.sq-field-wrapper .sqp-field-component:invalid + .sqp-input-error { +#sequra-page .sq-field-wrapper .sqp-field-component:invalid + .sqp-input-error { display: block; } -.sq-field-wrapper .sqp-input-error { +#sequra-page .sq-field-wrapper .sqp-input-error { display: none; margin-top: 4px; font-size: 13px; color: #e50000; } -.sq-field-wrapper.sqt--toggle .sqp-field-title { +#sequra-page .sq-field-wrapper.sqt--toggle .sqp-field-title { display: flex; align-items: center; justify-content: space-between; column-gap: 10px; } -.sq-field-wrapper.sqt--toggle .sqp-field-subtitle { +#sequra-page .sq-field-wrapper.sqt--toggle .sqp-field-subtitle { padding-right: 40px; } -.sq-field-wrapper.sqt--checkbox .sqp-description-wrapper { +#sequra-page .sq-field-wrapper.sqt--checkbox .sqp-description-wrapper { display: flex; flex-direction: row; column-gap: 8px; margin-top: 30px; } -.sq-field-wrapper.sqt--checkbox .sqp-description-wrapper.sqt--reverse { +#sequra-page .sq-field-wrapper.sqt--checkbox .sqp-description-wrapper.sqt--reverse { flex-direction: row-reverse; justify-content: flex-end; } -.sq-field-wrapper.sqt--checkbox .sqp-field-title { +#sequra-page .sq-field-wrapper.sqt--checkbox .sqp-field-title { display: flex; align-items: center; justify-content: space-between; column-gap: 10px; } -.sq-field-wrapper.sqt--checkbox .sqp-field-subtitle { +#sequra-page .sq-field-wrapper.sqt--checkbox .sqp-field-subtitle { padding-right: 40px; } -.sq-field-wrapper.sqs--error .sqp-field-component { +#sequra-page .sq-field-wrapper.sqs--error .sqp-field-component { border: 1px solid #e50000; color: #e50000; } -.sq-field-wrapper.sqs--error .sqp-input-error { +#sequra-page .sq-field-wrapper.sqs--error .sqp-input-error { display: block; } -.sq-field-wrapper.sqm--label-left { +#sequra-page .sq-field-wrapper.sqm--label-left { flex-direction: row; justify-content: space-between; } @media (max-width: 767.98px) { - .sq-field-wrapper.sqm--label-left { + #sequra-page .sq-field-wrapper.sqm--label-left { flex-direction: column; align-items: flex-start; } } -.sq-field-wrapper.sqm--label-left .sqp-field-component { +#sequra-page .sq-field-wrapper.sqm--label-left .sqp-field-component { min-width: 236px; min-height: 39px; column-gap: 32px; } @media (max-width: 767.98px) { - .sq-field-wrapper.sqm--label-left .sqp-field-component { + #sequra-page .sq-field-wrapper.sqm--label-left .sqp-field-component { width: 100%; } } -.sq-field-wrapper.sqm--block { +#sequra-page .sq-field-wrapper.sqm--block { display: block; } -.sq-field-wrapper.sqm--block.sqm--bellow-frame { +#sequra-page .sq-field-wrapper.sqm--block.sqm--bellow-frame { margin-top: 30px; } -.sq-field-wrapper .items { +#sequra-page .sq-field-wrapper .items { display: flex; align-items: center; column-gap: 1px; @@ -1313,7 +1392,7 @@ table tr th { max-width: 300px; width: 100%; } -.sq-field-wrapper .items span { +#sequra-page .sq-field-wrapper .items span { display: flex; justify-content: center; flex: 1; @@ -1322,112 +1401,78 @@ table tr th { padding: 8px; transition: all 0.2s; } -.sq-field-wrapper .items span.sqs--active { +#sequra-page .sq-field-wrapper .items span.sqs--active { color: #fff; background: #1c1c1c; } -.sq-field-wrapper .items span:hover, .sq-field-wrapper .items span:focus { +#sequra-page .sq-field-wrapper .items span:hover, #sequra-page .sq-field-wrapper .items span:focus { color: #fff; background: #1c1c1c; text-decoration: none; } -.sq-field-wrapper .sqp-menu-items-deployments { - display: flex; - align-items: center; - column-gap: 1px; - padding: 0; - border-radius: 50px; - overflow: hidden; - background-color: #f4f4f4; - max-width: 300px; - width: 100%; -} - -.sq-field-wrapper .sqp-menu-items-deployments span{ - display: flex; - justify-content: center; - flex: 1; - color: #656566; - font-size: 14px; - padding: 8px; - transition: all 0.2s; -} - -.sq-field-wrapper .sqp-menu-items-deployments span.sqs--active { - color: #ffffff; - background: #1c1c1c; -} - -.sq-field-wrapper .sqp-menu-items-deployments span:hover, -.sq-field-wrapper .sqp-menu-items-deployments span:focus { - color: #ffffff; - background: #1c1c1c; - text-decoration: none; -} - -.sq-country-field-wrapper { +#sequra-page .sq-country-field-wrapper { display: flex; column-gap: 18px; } @media (max-width: 499.98px) { - .sq-country-field-wrapper { + #sequra-page .sq-country-field-wrapper { flex-direction: column; row-gap: 6px; } } -.sq-data-frame { +#sequra-page .sq-data-frame { border: 1px solid #dce0e5; padding: 20px; border-radius: 6px; } -.sq-heading-wrapper { +#sequra-page .sq-heading-wrapper { display: flex; } -.sq-heading-wrapper .sqm-button { +#sequra-page .sq-heading-wrapper .sqm-button { margin-left: auto; width: auto; } -.sq-heading-wrapper .sqm-button.sqm--deployment { +#sequra-page .sq-heading-wrapper .sqm-button.sqm--deployment { width: 100%; } -.sq-heading-wrapper .sqm-button.sqm--deployment .sq-button { +#sequra-page .sq-heading-wrapper .sqm-button.sqm--deployment .sq-button { display: flex; margin-left: auto; } -.sq-table { +#sequra-page .sq-table { width: 100%; display: grid; } -.sq-table input { +#sequra-page .sq-table input { margin: 0; } -.sq-table .sq-table__row, -.sq-table .sq-table__header { +#sequra-page .sq-table .sq-table__row, +#sequra-page .sq-table .sq-table__header { display: flex; gap: 14px; align-items: center; width: 100%; } -.sq-table .sq-table__header { +#sequra-page .sq-table .sq-table__header { min-height: 2.5rem; box-sizing: border-box; border: none; position: relative; padding: 0 100px 0 0; } -.sq-table .sq-table__header .sq-table__header-item { +#sequra-page .sq-table .sq-table__header .sq-table__header-item { margin-bottom: 14px; } -.sq-table .sq-table__header .sq-button { +#sequra-page .sq-table .sq-table__header .sq-button { position: absolute; right: 0; top: 0; } -.sq-table .sq-table__footer { +#sequra-page .sq-table .sq-table__footer { width: 100%; padding: 14px 0 0 0; box-sizing: border-box; @@ -1435,16 +1480,16 @@ table tr th { flex-wrap: wrap; gap: 14px; } -.sq-table .sq-table__row { +#sequra-page .sq-table .sq-table__row { border-radius: 6px; border: 1px solid #dce0e5; padding: 14px; } -.sq-table .sq-table__row summary { +#sequra-page .sq-table .sq-table__row summary { list-style: none; position: relative; } -.sq-table .sq-table__row summary::after { +#sequra-page .sq-table .sq-table__row summary::after { font-family: "sq-icons"; content: "\e907"; line-height: 1; @@ -1458,13 +1503,13 @@ table tr th { transform-origin: center; cursor: pointer; } -.sq-table .sq-table__row[open] summary::after { +#sequra-page .sq-table .sq-table__row[open] summary::after { transform: translateY(-50%) rotate(-90deg); } -.sq-table .sq-table__row summary + .sq-table__row-content { +#sequra-page .sq-table .sq-table__row summary + .sq-table__row-content { margin-top: 14px; } -.sq-table .sq-table__row .sq-remove { +#sequra-page .sq-table .sq-table__row .sq-remove { min-height: 30px; padding: 6px 16px; align-self: flex-end; @@ -1475,12 +1520,12 @@ table tr th { margin-left: auto; float: right; } -.sq-table .sq-table__row .sq-remove:hover, .sq-table .sq-table__row .sq-remove:focus { +#sequra-page .sq-table .sq-table__row .sq-remove:hover, #sequra-page .sq-table .sq-table__row .sq-remove:focus { color: #fff; background-color: #c00e0e; border-color: #c00e0e; } -.sq-table .sq-table__row .sq-table__row-content { +#sequra-page .sq-table .sq-table__row .sq-table__row-content { display: flex; flex-direction: column; flex-grow: 1; @@ -1488,91 +1533,98 @@ table tr th { gap: 14px; padding: 14px 0; } -.sq-table .sq-table__row .sq-table__row-field-wrapper { +#sequra-page .sq-table .sq-table__row .sq-table__row-field-wrapper { display: grid; gap: 4px; + margin-bottom: 0; } -.sq-table .sq-table__row .sq-table__row-field-wrapper--grow { +#sequra-page .sq-table .sq-table__row .sq-table__row-field-wrapper--grow { flex-grow: 1; } -.sq-table .sq-table__row .sq-table__row-field-wrapper--space-between > .sqp-field-title { +#sequra-page .sq-table .sq-table__row .sq-table__row-field-wrapper--space-between > .sqp-field-title { display: flex; align-items: center; justify-content: space-between; font-size: 13px; } -.sq-table .sq-table__row .sq-table__row-field-label { +#sequra-page .sq-table .sq-table__row .sq-table__row-field-label { font-size: 13px; font-weight: 600; } -.sq-table .sq-table__row select { +#sequra-page .sq-table .sq-table__row select { font-size: 14px; min-height: 30px; - max-width: 45rem; + max-width: 450px; line-height: 2; -webkit-appearance: none; background: #fff url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E") no-repeat right 5px top 55%; background-size: 16px 16px; cursor: pointer; vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.sq-table .sq-table__row .sq-table__row-field { +#sequra-page .sq-table .sq-table__row .sq-table__row-field { padding: 6px 16px; color: #000; border: 1px solid #dce0e5; border-radius: 6px; + margin-left: 0; + margin-right: 0; + width: 90%; } -.sq-table .sq-table__body { +#sequra-page .sq-table .sq-table__body { display: grid; width: 100%; box-sizing: border-box; } -.sq-table .sq-table__body .sq-table__row:not(:last-child) { +#sequra-page .sq-table .sq-table__body .sq-table__row:not(:last-child) { border-bottom-width: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.sq-table .sq-table__body .sq-table__row:not(:last-child) + .sq-table__row { +#sequra-page .sq-table .sq-table__body .sq-table__row:not(:last-child) + .sq-table__row { border-top-left-radius: 0; border-top-right-radius: 0; } -.sq-table details.sq-table__row { +#sequra-page .sq-table details.sq-table__row { display: grid; gap: 0; } -.sq-alert { +#sequra-page .sq-alert { width: 100%; display: flex; justify-content: space-between; padding: 24px 19px 24px 24px; border-radius: 4px; } -.sq-alert.sqs--closed { +#sequra-page .sq-alert.sqs--closed { display: none; } -.sq-alert .sqp-alert-title { +#sequra-page .sq-alert .sqp-alert-title { display: flex; align-items: flex-start; font-size: 15px; line-height: 21px; } -.sq-alert .sqp-alert-title:before { +#sequra-page .sq-alert .sqp-alert-title:before { position: relative; top: 3px; } -.sq-alert .sqp-message { +#sequra-page .sq-alert .sqp-message { display: flex; flex-direction: column; } -.sq-alert .sqp-message .sqp-message-title { +#sequra-page .sq-alert .sqp-message .sqp-message-title { font-weight: 600; } -.sq-alert .sq-button { +#sequra-page .sq-alert .sq-button { padding: 7px; border: none; } -.sq-alert .sq-button span:before { +#sequra-page .sq-alert .sq-button span:before { display: flex; font-family: "sq-icons"; content: "\e90b"; @@ -1581,14 +1633,14 @@ table tr th { font-size: 7px; color: inherit; } -.sq-alert.sqt--success { +#sequra-page .sq-alert.sqt--success { background-color: #cef2dd; color: #055f29; } -.sq-alert.sqt--success .sqp-alert-title { +#sequra-page .sq-alert.sqt--success .sqp-alert-title { gap: 18px; } -.sq-alert.sqt--success .sqp-alert-title:before { +#sequra-page .sq-alert.sqt--success .sqp-alert-title:before { font-family: "sq-icons"; content: "\e91a"; line-height: 1; @@ -1596,28 +1648,28 @@ table tr th { font-size: 11px; top: 5px; } -.sq-alert.sqt--warning { +#sequra-page .sq-alert.sqt--warning { background-color: #ffeacc; color: #7F4A00; } -.sq-alert.sqt--warning .sqp-alert-title { +#sequra-page .sq-alert.sqt--warning .sqp-alert-title { gap: 17px; } -.sq-alert.sqt--warning .sqp-alert-title:before { +#sequra-page .sq-alert.sqt--warning .sqp-alert-title:before { font-family: "sq-icons"; content: "\e919"; line-height: 1; font-weight: 500; font-size: 13px; } -.sq-alert.sqt--error { +#sequra-page .sq-alert.sqt--error { background-color: #facccc; color: #720000; } -.sq-alert.sqt--error .sqp-alert-title { +#sequra-page .sq-alert.sqt--error .sqp-alert-title { gap: 16px; } -.sq-alert.sqt--error .sqp-alert-title:before { +#sequra-page .sq-alert.sqt--error .sqp-alert-title:before { font-family: "sq-icons"; content: "\e908"; line-height: 1; @@ -1625,7 +1677,7 @@ table tr th { font-size: 15px; } -.sq-page-footer { +#sequra-page .sq-page-footer { position: sticky; bottom: 0; width: 100%; @@ -1638,24 +1690,24 @@ table tr th { margin-top: -52px; background: #fff; } -.sq-page-footer .sqp-actions { +#sequra-page .sq-page-footer .sqp-actions { display: flex; gap: 8px; } -.sq-page-footer .sqp-actions .sq-button { +#sequra-page .sq-page-footer .sqp-actions .sq-button { font-weight: 400; } @media (max-width: 767.98px) { - .sq-page-footer { + #sequra-page .sq-page-footer { padding: 14px; } } -.sqs--hidden { +#sequra-page .sqs--hidden { display: none !important; } -.sq-version-badge { +#sequra-page .sq-version-badge { padding: 3px 8px; font-size: 13px; color: #394962; @@ -1663,7 +1715,7 @@ table tr th { border-radius: 15px; } -.sq-page-loader { +#sequra-page .sq-page-loader { position: fixed; z-index: 500; background-color: rgba(0, 17, 44, 0.4); @@ -1676,7 +1728,7 @@ table tr th { bottom: 0; } -.sq-wizard-sidebar { +#sequra-page .sq-wizard-sidebar { display: flex; flex-direction: column; flex-shrink: 0; @@ -1684,29 +1736,30 @@ table tr th { padding: 0 0 0 30px; background: #fff; } -.sq-wizard-sidebar .sqp-step { +#sequra-page .sq-wizard-sidebar .sqp-step { position: relative; margin-bottom: 16px; pointer-events: none; transition: color 0.2s; } -.sq-wizard-sidebar .sqp-step:hover { +#sequra-page .sq-wizard-sidebar .sqp-step:hover { text-decoration: none; } -.sq-wizard-sidebar .sqp-step:last-child { +#sequra-page .sq-wizard-sidebar .sqp-step:last-child { margin-bottom: 0; } -.sq-wizard-sidebar .sqp-step .sq-link-description { +#sequra-page .sq-wizard-sidebar .sqp-step .sq-link-description { font-weight: 400; color: #00112c; } -.sq-wizard-sidebar .sqp-step .sq-link-label { +#sequra-page .sq-wizard-sidebar .sqp-step .sq-link-label { font-weight: 600; display: block; color: #69778c; transition: color 0.2s; } -.sq-wizard-sidebar .sqp-step:before { +#sequra-page .sq-wizard-sidebar .sqp-step:before { + box-sizing: border-box; position: absolute; top: 5px; left: -22px; @@ -1719,16 +1772,18 @@ table tr th { border-radius: 100%; transition: background 0.2s; } -.sq-wizard-sidebar .sqp-step.sqs--completed:not(:first-child):not(:last-child) { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--completed:not(:first-child):not(:last-child) { pointer-events: all; } -.sq-wizard-sidebar .sqp-step.sqs--completed .sq-link-label { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--completed .sq-link-label { color: #00112c; } -.sq-wizard-sidebar .sqp-step.sqs--completed:before { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--completed:before { + box-sizing: border-box; background: #00C2A3; } -.sq-wizard-sidebar .sqp-step.sqs--completed:after { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--completed:after { + box-sizing: border-box; position: absolute; top: 12px; left: -17px; @@ -1738,14 +1793,16 @@ table tr th { content: ""; background: #00C2A3; } -.sq-wizard-sidebar .sqp-step.sqs--active .sq-link-label { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--active .sq-link-label { color: #00112c; } -.sq-wizard-sidebar .sqp-step.sqs--active:before { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--active:before { + box-sizing: border-box; z-index: 4; background: #00C2A3; } -.sq-wizard-sidebar .sqp-step.sqs--active:after { +#sequra-page .sq-wizard-sidebar .sqp-step.sqs--active:after { + box-sizing: border-box; position: absolute; top: 3px; left: -24px; @@ -1757,7 +1814,7 @@ table tr th { border-radius: 100%; } -.sq-settings-sidebar { +#sequra-page .sq-settings-sidebar { display: flex; flex-direction: column; gap: 0; @@ -1768,23 +1825,23 @@ table tr th { border-radius: 6px; } @media (max-width: 1023.98px) { - .sq-settings-sidebar { + #sequra-page .sq-settings-sidebar { width: 100%; } } -.sq-settings-sidebar .sq-sidebar-item { +#sequra-page .sq-settings-sidebar .sq-sidebar-item { display: flex; align-items: center; column-gap: 10px; transition: background 0.2s; } -.sq-settings-sidebar .sq-sidebar-item:hover { +#sequra-page .sq-settings-sidebar .sq-sidebar-item:hover { background: #f3f6f9; } -.sq-settings-sidebar .sq-sidebar-item.sqs--active { +#sequra-page .sq-settings-sidebar .sq-sidebar-item.sqs--active { background: #f3f6f9; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link { position: relative; transition: color 0.2s; padding: 10px 20px; @@ -1793,41 +1850,41 @@ table tr th { align-items: center; text-decoration: none; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link:before { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link:before { position: relative; display: inline-block; margin-right: 8px; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link:last-child { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link:last-child { margin-bottom: 0; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link .sq-link-label { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link .sq-link-label { display: block; color: #394962; transition: color 0.2s; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--general:before { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--general:before { font-family: "sq-icons"; content: "\e903"; line-height: 1; font-weight: 500; font-size: 20px; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--connection:before { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--connection:before { font-family: "sq-icons"; content: "\e902"; line-height: 1; font-weight: 500; font-size: 20px; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--order:before { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--order:before { font-family: "sq-icons"; content: "\e900"; line-height: 1; font-weight: 500; font-size: 20px; } -.sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--widget:before { +#sequra-page .sq-settings-sidebar .sq-sidebar-item .sq-sidebar-link.sqm--widget:before { font-family: "sq-icons"; content: "\e901"; line-height: 1; @@ -1835,11 +1892,142 @@ table tr th { font-size: 20px; } -* + .sqp-flash-message-wrapper, .sqp-flash-message-wrapper + * { +#sequra-page * + .sqp-flash-message-wrapper, +#sequra-page .sqp-flash-message-wrapper + * { margin-top: 20px; } -.sq-table-container { +tr:has(.sqm--log):hover { + background: #f4f4f4; +} + +#sequra-page .sqm--log { + font-family: monospace; + font-size: 12px; +} +#sequra-page .sqm--log-info { + color: #2196f3; +} +#sequra-page .sqm--log-warning { + color: #ff9800; +} +#sequra-page .sqm--log-error { + color: #f44336; +} +#sequra-page .sqm--log-debug { + color: #000; +} +#sequra-page .sqm--log-details { + display: flex; + align-items: center; + justify-content: center; + margin-left: 6px; + background: #fff; + border-radius: 20px; + width: 20px; + height: 20px; + padding: 0; + border-color: transparent; +} +#sequra-page .sqm--log-details::before { + font-family: "sq-icons"; + content: "\e91b"; + line-height: 1; + font-weight: 500; + font-size: 14px; +} +#sequra-page .sqm--log-details > span { + display: none; +} +#sequra-page .sqm--log-details.sqm--log-details-open { + background: #000; +} +#sequra-page .sqm--log-details.sqm--log-details-open::before { + color: #fff; +} + +#sequra-page .sq-datatable__top { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 2rem; + font-size: 15px; + align-items: center; +} +#sequra-page .sq-datatable__bottom { + margin-top: 2rem; + display: flex; + justify-content: space-between; + gap: 1rem; + font-size: 15px; + align-items: center; +} +#sequra-page .sq-datatable__info { + color: #686565; +} +#sequra-page .sq-datatable__pagination { + justify-self: flex-end; +} +#sequra-page .sq-datatable__pagination-list { + display: flex; + margin-left: auto; + margin-right: 0; +} +#sequra-page .sq-datatable__pagination-list .datatable-pagination-list-item .datatable-pagination-list-item-link { + width: 2rem; + height: 2rem; + border-radius: 2rem; + color: #000; + transition: all 0.15s; + margin: 0 0.25rem; + cursor: pointer; +} +#sequra-page .sq-datatable__pagination-list .datatable-pagination-list-item.sq-datatable__active .datatable-pagination-list-item-link, #sequra-page .sq-datatable__pagination-list .datatable-pagination-list-item:hover:not(.datatable-disabled) .datatable-pagination-list-item-link { + background-color: #000; + color: #fff; +} +#sequra-page .sq-datatable__pagination-list .datatable-pagination-list-item.datatable-disabled .datatable-pagination-list-item-link { + color: #686565; + cursor: not-allowed; +} +#sequra-page .sq-datatable__search { + justify-self: flex-end; + display: flex; + align-items: center; + gap: 1rem; +} +#sequra-page .sq-datatable__search .sq-field-wrapper { + margin-bottom: 0 !important; +} +#sequra-page .sq-datatable__search .sq-button { + padding: 8px 24px !important; +} +#sequra-page .sq-datatable__selector { + padding: 8px 16px !important; + color: #000 !important; + border: 1px solid #dce0e5 !important; + border-radius: 6px !important; + min-width: 80px !important; + outline-color: transparent !important; +} +#sequra-page .sq-datatable__selector:focus, #sequra-page .sq-datatable__selector:hover, #sequra-page .sq-datatable__selector:active { + border-color: #6fcf97 !important; + box-shadow: none !important; +} +#sequra-page .sq-datatable__input { + padding: 8px 16px !important; + color: #000 !important; + border: 1px solid #dce0e5 !important; + border-radius: 6px !important; + min-width: 300px !important; + outline-color: transparent !important; +} +#sequra-page .sq-datatable__input:focus, #sequra-page .sq-datatable__input:hover, #sequra-page .sq-datatable__input:active { + border-color: #6fcf97 !important; + box-shadow: none !important; +} + +#sequra-page .sq-table-container { overflow-x: auto; width: 100%; margin: 0; @@ -1849,85 +2037,115 @@ table tr th { font-size: 13px; } @media (max-width: 1023.98px) { - .sq-table-container { + #sequra-page .sq-table-container { padding: 0; } } -.sq-table-container table { +#sequra-page .sq-table-container table { min-width: 780px; } -.sq-table-container table .sqm--text-left { +#sequra-page .sq-table-container table .sqm--text-left { text-align: left; } -.sq-table-container table tr .sq-button { +#sequra-page .sq-table-container table tr .sq-button { font-size: inherit; display: inline-flex; align-items: center; column-gap: 8px; } -.sq-table-container .sqp-payment-method-header-cell { +#sequra-page .sq-table-container .sqp-payment-method-header-cell { width: 50%; } -.sq-table-container .sqp-payment-method-cell { +#sequra-page .sq-table-container .sqp-payment-method-cell { display: flex; align-items: center; column-gap: 8px; } -.sq-table-container .sqp-payment-method-cell svg { +#sequra-page .sq-table-container .sqp-payment-method-cell svg { flex-shrink: 0; } -.sq-table-container .sqp-payment-method-cell .sqp-payment-method-image { +#sequra-page .sq-table-container .sqp-payment-method-cell .sqp-payment-method-image { width: 52px; height: 52px; } -.sq-table-container .sqp-payment-method-cell .sqp-payment-method-title { +#sequra-page .sq-table-container .sqp-payment-method-cell .sqp-payment-method-title { font-weight: 700; margin: 0 0 3px; font-size: 13px; } -.sq-table-container .sqp-payment-method-cell .sqp-payment-method-description { +#sequra-page .sq-table-container .sqp-payment-method-cell .sqp-payment-method-description { font-size: 13px; margin: 0; } +#sequra-page .sq-table-container .sqp-details { + background-color: #f3f6f9; +} +#sequra-page .sq-table-container .sqp-details .sq-link-button { + color: #0075ff; + text-decoration: none; + display: flex; +} +#sequra-page .sq-table-container .sqp-details .sq-link-button::after { + font-family: "sq-icons"; + content: "\e916"; + line-height: 1; + font-weight: 500; + font-size: 6px; + padding: 5px 0 0 4px; +} +#sequra-page .sq-table-container .sqp-details-wrapper { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; +} +#sequra-page .sq-table-container .sqp-details-row { + display: flex; + justify-content: space-between; + gap: 30px; +} +#sequra-page .sq-table-container .sqp-details-info { + display: flex; + gap: 5px; +} +#sequra-page .sq-table-container .sqp-details-message { + white-space: normal; + text-align: justify; +} +#sequra-page .sq-table-container .sqp-details-label { + font-weight: 700; +} -.sq-modal { - position: absolute; +#sequra-page .sq-modal { + position: fixed; top: 0; bottom: 0; left: 0; right: 0; + background-color: rgba(0, 17, 44, 0.4); z-index: 100; display: flex; align-items: center; justify-content: center; } @media (max-width: 767.98px) { - .sq-modal { + #sequra-page .sq-modal { padding: 10px; } } -.sq-modal.sqv--connection-modal { - height: 250px; - width: 600px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} -.sq-modal .sqp-modal-content { +#sequra-page .sq-modal .sqp-modal-content { display: flex; flex-direction: column; align-items: center; max-width: 100%; - width: 100%; max-height: 100%; - height: 100%; position: relative; margin: 0 auto; background: #fff; box-shadow: 0 18px 6px rgba(0, 0, 0, 0.06); border-radius: 10px; } -.sq-modal .sqp-modal-content .sqp-close-button { +#sequra-page .sq-modal .sqp-modal-content .sqp-close-button { position: absolute; width: 24px; height: 24px; @@ -1939,14 +2157,14 @@ table tr th { color: #394962; z-index: 1; } -.sq-modal .sqp-modal-content .sqp-close-button span:before { +#sequra-page .sq-modal .sqp-modal-content .sqp-close-button span:before { font-family: "sq-icons"; content: "\e90a"; line-height: 1; font-weight: 500; font-size: 20px; } -.sq-modal .sqp-modal-content .sqp-title { +#sequra-page .sq-modal .sqp-modal-content .sqp-title { font-weight: 600; color: #00112c; font-size: 20px; @@ -1954,7 +2172,7 @@ table tr th { padding: 16px 40px 16px 24px; width: 100%; } -.sq-modal .sqp-modal-content .sqp-body { +#sequra-page .sq-modal .sqp-modal-content .sqp-body { font-weight: 400; align-items: flex-start; justify-content: flex-start; @@ -1964,10 +2182,10 @@ table tr th { overflow: hidden auto; font-size: 15px; } -.sq-modal .sqp-modal-content .sqp-body.sqm--full-width { +#sequra-page .sq-modal .sqp-modal-content .sqp-body.sqm--full-width { padding: 20px 0; } -.sq-modal .sqp-modal-content .sqp-footer { +#sequra-page .sq-modal .sqp-modal-content .sqp-footer { position: sticky; bottom: 0; padding: 24px; @@ -1978,3 +2196,8 @@ table tr th { flex-shrink: 0; gap: 10px; } +#sequra-page .sq-modal .sqp-modal-content .sqv--deployments { + padding-left: 0 !important; + padding-right: 0 !important; + padding-bottom: 0 !important; +} diff --git a/view/adminhtml/web/js/AdvancedController.js b/view/adminhtml/web/js/AdvancedController.js new file mode 100644 index 00000000..f9f5e79d --- /dev/null +++ b/view/adminhtml/web/js/AdvancedController.js @@ -0,0 +1,435 @@ +import { DataTable } from 'simple-datatables'; +import JSONFormatter from 'json-formatter-js' + +if (!window.SequraFE) { + window.SequraFE = {}; +} + +(function () { + + /** + * @typedef Log + * @property {string} type + * @property {string} datetime + * @property {string} message + * @property {string} context + */ + + /** + * @typedef LogSettings + * @property {boolean} isEnabled + * @property {number} level + */ + + /** + * Handles debug logs page logic. + * + * @param {{ + * getLogsUrl: string, + * }} configuration + * @constructor + */ + function AdvancedController(configuration) { + const { templateService, elementGenerator: generator, components, utilities } = SequraFE; + /** @type AjaxServiceType */ + const api = SequraFE.ajaxService; + /** @type string */ + let currentStoreId = ''; + /** @type Version */ + let version; + /** @type Store[] */ + let stores; + /** @type ConnectionSettings */ + let connectionSettings; + /** @type LogSettings */ + let logsSettings; + /** @type string[] */ + let logs; + /** @type string */ + let logList; + + /** @type DataTable */ + let dataTable; + + const wrapperClass = 'sqp-datatable-wrapper'; + + /** + * Displays page content. + * + * @param {{ state?: string, storeId: string }} config + */ + this.display = ({ storeId }) => { + + currentStoreId = storeId; + templateService.clearMainPage(); + + stores = SequraFE.state.getData('stores'); + version = SequraFE.state.getData('version'); + connectionSettings = SequraFE.state.getData('connectionSettings'); + logsSettings = SequraFE.state.getData('logsSettings'); + + logs = SequraFE.state.getData('logs'); + + if (!logs) { + api.get(configuration.getLogsUrl, null, SequraFE.customHeader) + .then(logsRes => { + SequraFE.state.setData('logs', logsRes) + logs = logsRes; + }) + .catch(error => console.error(error)) + .finally(() => { + initializePage(); + utilities.hideLoader() + }); + } else { + initializePage(); + utilities.hideLoader(); + } + }; + + /** + * Renders the page contents. + */ + const initializePage = () => { + const pageWrapper = document.getElementById('sq-page-wrapper'); + + pageWrapper.append( + generator.createElement('div', 'sq-page-content-wrapper sqv--advanced', '', null, [ + SequraFE.components.PageHeader.create( + { + currentVersion: version?.current, + newVersion: { + versionLabel: version?.new, + versionUrl: version?.downloadNewVersionUrl + }, + mode: connectionSettings.environment === 'live' ? connectionSettings.environment : 'test', + activeStore: currentStoreId, + stores: stores.map((store) => ({ label: store.storeName, value: store.storeId })), + onChange: (storeId) => { + if (storeId !== SequraFE.state.getStoreId()) { + SequraFE.state.setStoreId(storeId); + window.location.hash = ''; + SequraFE.state.display(); + } + }, + menuItems: SequraFE.utilities.getMenuItems(SequraFE.appStates.ADVANCED) + } + ), + generator.createElement('div', 'sq-page-content', '', null, [ + generator.createElement('div', 'sq-content-row', '', null, [ + generator.createElement('main', 'sq-content', '', null, [ + generator.createElement('div', 'sq-content-inner', '', null, [ + generator.createElement('div', 'sqp-flash-message-wrapper'), + generator.createElement('div', 'sq-table-heading', '', null, [ + generator.createPageHeading({ + title: 'general.advanced' + }), + ]), + generator.createToggleField({ + className: 'sq-log-settings-toggle', + value: logsSettings.isEnabled, + label: 'debug.title', + description: 'debug.description', + onChange: handleEnableDebugChange + }), + generator.createDropdownField({ + label: 'debug.log.minLevel', + description: 'debug.log.minLevelDescription', + value: logsSettings.level, + options: getLogLevelOptions(), + variation: "label-left", + onChange: handleLogLevelChange + }), + generator.createElement('div', wrapperClass), + ]), + ]) + ]) + ]), + generator.createSupportLink() + ])); + + initializeDataTable(); + } + + /** + * Returns log level options. + * + * @returns {[{label: string, value: string}]} + */ + const getLogLevelOptions = () => [ + { label: 'DEBUG', value: 3 }, + { label: 'INFO', value: 2 }, + { label: 'WARNING', value: 1 }, + { label: 'ERROR', value: 0 }, + ] + + /** + * Returns table headers. + * + * @returns {TableCell[]} + */ + const getTableHeaders = () => { + return [ + { label: 'debug.log.severity', className: 'sqm--text-left' }, + { label: 'debug.log.datetime', className: 'sqm--text-left' }, + { label: 'debug.log.message', className: 'sqm--text-left' }, + ]; + } + + /** + * Parse raw log data and return an object. + * + * @returns {Log} + */ + const parseLog = (raw) => { + const data = raw.split('\t'); + return { + type: data[0].trim(), + datetime: data[1].trim(), + message: data[2].trim(), + context: data[3].trim() + }; + } + + + /** @param {EventTarget} btn */ + const renderDetails = (btn) => { + btn.classList.toggle('sqm--log-details-open'); + const container = btn.parentElement.querySelector('.sqm--log-context'); + container.style.display = btn.classList.contains('sqm--log-details-open') ? 'block' : 'none'; + + if (!container.firstChild) { + let json = ''; + try { + json = JSON.parse(logList[btn.getAttribute('data-index')].context); + } catch (e) { + json = 'Error parsing log content: ' + e.message; + } + const formatter = new JSONFormatter(json, 0); + container.appendChild(formatter.render()); + } + } + + /** + * Returns table rows. + * + * @param {string[]} logs + * + * @returns {TableCell[][]} + */ + const getTableRows = (logs) => { + logList = logs.map(rawLog => parseLog(rawLog)); + + return logList.map((log, index) => { + const className = `sqm--text-left sqm--log sqm--log-${log.type.toLowerCase()}`; + + return [ + { label: log.type, className }, + { label: log.datetime, className }, + { + label: log.message, + className: `${className} sqm--log-index-${index}`, + renderer: (cell) => { + if ('' != log.context) { + const btn = generator.createButton({ + type: 'text', + className: 'sqm--log-details', + }); + btn.setAttribute('data-index', index); + cell.append(btn); + cell.append(generator.createElement('div', 'sqm--log-context')); + } + return cell; + } + } + ]; + }); + } + + const initializeDataTable = () => { + if (dataTable) { + dataTable.destroy(); + dataTable = null; + } + + const wrapper = document.querySelector(`.${wrapperClass}`); + if (wrapper) { + SequraFE.templateService.clearComponent(wrapper); + } + + wrapper.append(components.DataTable.create(getTableHeaders(), logs?.length ? getTableRows(logs) : [])); + + dataTable = new DataTable(`.${wrapperClass} table`, { + classes: { + active: "sq-datatable__active", + bottom: "sq-datatable__bottom", + container: "sq-datatable__container", + cursor: "sq-datatable__cursor", + dropdown: "sq-datatable__dropdown", + ellipsis: "sq-datatable__ellipsis", + empty: "sq-datatable__empty", + headercontainer: "sq-datatable__headercontainer", + info: "sq-datatable__info", + input: "sq-datatable__input", + loading: "sq-datatable__loading", + pagination: "sq-datatable__pagination", + paginationList: "sq-datatable__pagination-list", + search: "sq-datatable__search", + selector: "sq-datatable__selector", + sorter: "sq-datatable__sorter", + table: "sq-datatable__table", + top: "sq-datatable__top", + wrapper: "sq-datatable__wrapper" + } + }); + + const searchWrapper = document.querySelector('.sq-datatable__search'); + if (!searchWrapper) { + return; + } + searchWrapper.append( + generator.createButtonField({ + className: 'sq-controls sqm--block sqm--block-inline', + buttonLabel: 'debug.reload', + onClick: handleReloadLogs + }), + generator.createButtonField({ + className: 'sq-controls sqm--block sqm--block-inline', + buttonType: 'danger', + buttonLabel: 'debug.remove', + onClick: handleRemoveLogs + }) + ); + + const resetDetails = () => { + document.querySelectorAll('.sqm--log-context').forEach(elem => { + elem.style.display = 'none' + elem.innerHTML = ''; + }); + document.querySelectorAll('.sqm--log-details').forEach(elem => { + elem.classList.remove('sqm--log-details-open'); + }); + } + + dataTable.on('datatable.init', resetDetails); + dataTable.on('datatable.page', resetDetails); + dataTable.on('datatable.selectrow', (rowIndex, event) => { + if (event.target.classList.contains('sqm--log-details')) { + renderDetails(event.target); + } + }); + } + + /** + * Save logs settings. + * @param {LogSettings} settings + */ + const saveLogsSettings = (settings) => { + utilities.showLoader(); + api.post(configuration.saveLogsSettingsUrl, settings, SequraFE.customHeader) + .then(response => { + logsSettings = settings; + SequraFE.state.setData('logsSettings', logsSettings); + }).catch(error => { + console.error(error); + document.querySelector('.sq-log-settings-toggle input').checked = !settings.isEnabled; + showFlashMessage('general.errors.unknown', 'error'); + }) + .finally(() => utilities.hideLoader()); + } + + const handleEnableDebugChange = (value) => saveLogsSettings({ + ...logsSettings, + isEnabled: value + }); + + const handleLogLevelChange = (value) => saveLogsSettings({ + ...logsSettings, + level: parseInt(value) + }); + + const showFlashMessage = (message, status) => { + const container = document.querySelector('.sqp-flash-message-wrapper'); + if (!container) { + return; + } + + SequraFE.templateService.clearComponent(container); + + container.prepend(SequraFE.utilities.createFlashMessage(message, status)); + } + + /** + * Shows the confirm remove modal. + * + * @returns {Promise} + */ + const showConfirmRemoveModal = () => { + return new Promise((resolve) => { + const modal = components.Modal.create({ + title: "debug.log.removeModal.title", + className: "sq-modal", + content: [generator.createElement('p', '', "debug.log.removeModal.message")], + footer: true, + canClose: false, + buttons: [ + { + type: 'default', + label: 'general.cancel', + onClick: () => { + modal.close(); + resolve(false); + } + }, + { + type: 'danger', + label: 'debug.remove', + onClick: () => { + modal.close(); + resolve(true); + } + } + ] + }); + + modal.open(); + }); + } + + const handleRemoveLogs = () => { + showConfirmRemoveModal().then((confirmed) => { + if (!confirmed) { + return; + } + + utilities.showLoader(); + api.delete(configuration.removeLogsUrl, null, null, SequraFE.customHeader) + .then(_ => { + logs = []; + SequraFE.state.setData('logs', logs) + initializeDataTable(); + }).catch(error => { + console.error(error); + showFlashMessage('general.errors.failedToRemoveLog', 'error'); + }) + .finally(() => utilities.hideLoader()); + }); + } + + const handleReloadLogs = () => { + utilities.showLoader(); + api.get(configuration.getLogsUrl, null, SequraFE.customHeader) + .then(logsRes => { + logs = logsRes; + SequraFE.state.setData('logs', logs) + initializeDataTable(); + }).catch(error => { + console.error(error); + showFlashMessage('general.errors.failedToRetrieveLog', 'error'); + }) + .finally(() => utilities.hideLoader()); + } + } + + SequraFE.AdvancedController = AdvancedController; +})(); diff --git a/view/adminhtml/web/js/AjaxService.js b/view/adminhtml/web/js/AjaxService.js index 4d154f54..f25d328d 100644 --- a/view/adminhtml/web/js/AjaxService.js +++ b/view/adminhtml/web/js/AjaxService.js @@ -1,6 +1,10 @@ if (!window.SequraFE) { window.SequraFE = {}; } +if (!window.SequraFE.customHeader) { + // Init the custom header if it doesn't exist so it will be available for the controllers + window.SequraFE.customHeader = {}; +} /** * @typedef AjaxServiceType @@ -83,8 +87,9 @@ if (!window.SequraFE) { * @param {string} url The URL to call. * @param {Record?} data * @param {(error: Record) => Promise?} errorCallback + * @param {Record?} customHeader */ - const del = (url, data, errorCallback) => call('DELETE', url, data, errorCallback); + const del = (url, data, errorCallback, customHeader = {}) => call('DELETE', url, data, errorCallback, customHeader); /** * Performs ajax call. diff --git a/view/adminhtml/web/js/ConnectionSettingsForm.js b/view/adminhtml/web/js/ConnectionSettingsForm.js index b9dc8280..f41d0822 100644 --- a/view/adminhtml/web/js/ConnectionSettingsForm.js +++ b/view/adminhtml/web/js/ConnectionSettingsForm.js @@ -374,7 +374,6 @@ if (!window.SequraFE) { document.querySelector(`[name="${name}-input"]`), 'validation.requiredField' ); - const current = getSettingsForActiveDeployment(changedSettings); if (current) current[name] = value; } @@ -496,7 +495,7 @@ if (!window.SequraFE) { const connect = () => { utilities.showLoader(); - api.post(configuration.connectUrl, changedSettings) + api.post(configuration.connectUrl, changedSettings, SequraFE.customHeader) .then((result) => { if (!areCredentialsValid(result)) { @@ -527,11 +526,20 @@ if (!window.SequraFE) { disableFooter(true); - if (configuration.appState === SequraFE.appStates.SETTINGS && navigateToOnboarding) { - SequraFE.state.setCredentialsChanged(); - SequraFE.state.goToState(SequraFE.appStates.ONBOARDING); + if ( configuration.appState === SequraFE.appStates.SETTINGS) { + if(navigateToOnboarding){ + SequraFE.state.setCredentialsChanged(); + SequraFE.state.goToState(SequraFE.appStates.ONBOARDING); + return; + } + // Reload GeneralSettings data. + api.get(configuration.getGeneralSettingsUrl, null, SequraFE.customHeader).then(generalSettings => { + SequraFE.state.setData('generalSettings', generalSettings); + }).catch(() => { + SequraFE.responseService.errorHandler({ errorCode: 'general.errors.backgroundDataFetchFailure' }).catch(e => console.error(e)); + }).finally(() => utilities.hideLoader()); } else { - utilities.hideLoader(); + utilities.hideLoader(); } }); } @@ -547,7 +555,7 @@ if (!window.SequraFE) { utilities.showLoader(); - api.post(configuration.disconnectUrl, createPayload()) + api.post(configuration.disconnectUrl, createPayload(), SequraFE.customHeader) .then(() => SequraFE.state.display()) .finally(utilities.hideLoader); }) @@ -590,6 +598,7 @@ if (!window.SequraFE) { className: `sq-modal sqv--connection-modal`, content: [generator.createElement('p', '', `connection.disconnect.message`)], footer: true, + canClose: false, buttons: [ { type: 'secondary', @@ -600,7 +609,7 @@ if (!window.SequraFE) { } }, { - type: 'primary', + type: 'danger', label: 'general.confirm', onClick: () => { modal.close(); @@ -626,6 +635,7 @@ if (!window.SequraFE) { className: `sq-modal sqv--connection-modal`, content: [generator.createElement('p', '', `connection.modal.message`)], footer: true, + canClose: false, buttons: [ { type: 'secondary', diff --git a/view/adminhtml/web/js/DeploymentsModalForm.js b/view/adminhtml/web/js/DeploymentsModalForm.js index 0c537586..06af9e1c 100644 --- a/view/adminhtml/web/js/DeploymentsModalForm.js +++ b/view/adminhtml/web/js/DeploymentsModalForm.js @@ -152,7 +152,7 @@ window.SequraFE.showDeploymentsModal = function ( finalSettings.connectionData.push(updatedConnection); } - const result = await api.post(configuration.connectUrl, finalSettings); + const result = await api.post(configuration.connectUrl, finalSettings, SequraFE.customHeader); if (!areCredentialsValid(result)) { handleValidationError(); @@ -214,6 +214,7 @@ window.SequraFE.showDeploymentsModal = function ( className: 'sq-modal', content: [content], footer: true, + canClose: false, buttons: [ { type: 'secondary', diff --git a/view/adminhtml/web/js/ElementGenerator.js b/view/adminhtml/web/js/ElementGenerator.js index 2741583d..417486ae 100644 --- a/view/adminhtml/web/js/ElementGenerator.js +++ b/view/adminhtml/web/js/ElementGenerator.js @@ -90,26 +90,26 @@ if (!window.SequraFE) { * className?: string, [key: string]: any, onClick?: () => void}} props * @return {HTMLButtonElement} */ - const createButton = ({type, size, className, onClick, label, ...properties}) => { + const createButton = ({ type, size, className, onClick, label, ...properties }) => { const cssClass = ['sq-button']; type && cssClass.push('sqt--' + type); size && cssClass.push('sqm--' + size); className && cssClass.push(className); - const button = createElement('button', cssClass.join(' '), '', {type: 'button', ...properties}, [ + const button = createElement('button', cssClass.join(' '), '', { type: 'button', ...properties }, [ createElement('span', '', label) ]); onClick && - button.addEventListener( - 'click', - (event) => { - event.stopPropagation(); - event.preventDefault(); - onClick(); - }, - false - ); + button.addEventListener( + 'click', + (event) => { + event.stopPropagation(); + event.preventDefault(); + onClick(); + }, + false + ); return button; }; @@ -131,7 +131,7 @@ if (!window.SequraFE) { * string, [key: string]: any }} props * @return {HTMLElement} */ - const createLoader = ({type, variation}) => { + const createLoader = ({ type, variation }) => { const cssClass = ['sq-loader']; type && cssClass.push('sqt--' + type); variation && cssClass.push('sqm--' + variation); @@ -145,7 +145,7 @@ if (!window.SequraFE) { * @param {{text?: string, className?: string, href: string, downloadFile?: string, openInNewTab?: boolean}} props * @return {HTMLLinkElement} */ - const createButtonLink = ({text, className = '', href, downloadFile, openInNewTab}) => { + const createButtonLink = ({ text, className = '', href, downloadFile, openInNewTab }) => { const link = createElement('a', className, '', { href: href, target: openInNewTab ? "_blank" : "" @@ -216,7 +216,7 @@ if (!window.SequraFE) { * @param {ElementProps & DropdownComponentModel} props The properties. * @return {HTMLDivElement} */ - const createDropdownField = ({className = '', label, description, variation, error, ...dropdownProps}) => { + const createDropdownField = ({ className = '', label, description, variation, error, ...dropdownProps }) => { return createFieldWrapper( SequraFE.components.Dropdown.create(dropdownProps), label, @@ -233,9 +233,9 @@ if (!window.SequraFE) { * @param {ElementProps} props The properties. * @return {HTMLElement} */ - const createPasswordField = ({className = '', label, description, variation, error, onChange, ...rest}) => { + const createPasswordField = ({ className = '', label, description, variation, error, onChange, ...rest }) => { const wrapper = createElement('div', `sq-password ${className}`); - const input = createElement('input', 'sqp-field-component', '', {type: 'password', ...rest}); + const input = createElement('input', 'sqp-field-component', '', { type: 'password', ...rest }); const span = createElement('span'); span.addEventListener('click', () => { if (input.type === 'password') { @@ -257,9 +257,9 @@ if (!window.SequraFE) { * @param {ElementProps & { type?: 'text' | 'number', variation?: 'label-left' }} props The properties. * @return {HTMLElement} */ - const createTextField = ({className = '', label, description, variation, error, onChange, ...rest}) => { + const createTextField = ({ className = '', label, description, variation, error, onChange, ...rest }) => { /** @type HTMLInputElement */ - const input = createElement('input', `sqp-field-component ${className}`, '', {type: 'text', ...rest}); + const input = createElement('input', `sqp-field-component ${className}`, '', { type: 'text', ...rest }); onChange && input.addEventListener('change', (event) => onChange(event.currentTarget?.value)); return createFieldWrapper(input, label, description, variation, error, ''); @@ -271,9 +271,9 @@ if (!window.SequraFE) { * @param {ElementProps & { type?: 'text' | 'number', variation?: 'label-left' }} props The properties. * @return {HTMLElement} */ - const createTextArea = ({className = '', label, description, variation, error, onChange, ...rest}) => { + const createTextArea = ({ className = '', label, description, variation, error, onChange, ...rest }) => { /** @type HTMLInputElement */ - const textArea = createElement('textarea', `sqp-field-component ${className}`, '', {...rest}); + const textArea = createElement('textarea', `sqp-field-component ${className}`, '', { ...rest }); onChange && textArea.addEventListener('change', (event) => onChange(event.currentTarget?.value)); return createFieldWrapper(textArea, label, description, variation, error, 'sqp-textarea-field'); @@ -286,7 +286,7 @@ if (!window.SequraFE) { * @param {(value: string) => void?} onChange * @return {HTMLElement} */ - const createCountryField = ({countryCode, merchantId, onChange}) => { + const createCountryField = ({ countryCode, merchantId, onChange }) => { const code = countryCode.toUpperCase(); return createElement('div', 'sq-country-field-wrapper sqs--hidden', '', null, [ createTextField({ @@ -308,7 +308,7 @@ if (!window.SequraFE) { * @return {HTMLElement} */ const createNumberField = (props) => { - return createTextField({type: 'number', step: '0.01', ...props}); + return createTextField({ type: 'number', step: '0.01', ...props }); }; /** @@ -317,11 +317,11 @@ if (!window.SequraFE) { * @param {ElementProps} props The properties. * @return {HTMLElement} */ - const createRadioGroupField = ({name, value, className, options, label, description, error, onChange}) => { + const createRadioGroupField = ({ name, value, className, options, label, description, error, onChange }) => { const wrapper = createElement('div', 'sq-radio-input-group'); options.forEach((option) => { const label = createElement('label', 'sq-radio-input'); - const props = {type: 'radio', value: option.value, name}; + const props = { type: 'radio', value: option.value, name }; if (value === option.value) { props.checked = 'checked'; } @@ -340,9 +340,9 @@ if (!window.SequraFE) { * @param {ElementProps} props The properties. * @return {HTMLElement} */ - const createToggleField = ({className = '', label, description, error, onChange, value, ...rest}) => { + const createToggleField = ({ className = '', label, description, error, onChange, value, ...rest }) => { /** @type HTMLInputElement */ - const checkbox = createElement('input', 'sqp-toggle-input', '', {type: 'checkbox', checked: value, ...rest}); + const checkbox = createElement('input', 'sqp-toggle-input', '', { type: 'checkbox', checked: value, ...rest }); onChange && checkbox.addEventListener('change', () => onChange(checkbox.checked)); const field = createElement('div', className + ' sq-field-wrapper sqt--toggle', '', null, [ @@ -368,9 +368,9 @@ if (!window.SequraFE) { * @param {ElementProps} props The properties. * @return {HTMLElement} */ - const createCheckboxField = ({className = '', label, description, error, onChange, value, ...rest}) => { + const createCheckboxField = ({ className = '', label, description, error, onChange, value, ...rest }) => { /** @type HTMLInputElement */ - const checkbox = createElement('input', 'sqp-checkbox-input', '', {type: 'checkbox', checked: value, ...rest}); + const checkbox = createElement('input', 'sqp-checkbox-input', '', { type: 'checkbox', checked: value, ...rest }); onChange && checkbox.addEventListener('change', () => onChange(checkbox.checked)); const field = createElement('div', className + ' sq-field-wrapper sqt--checkbox'); @@ -428,7 +428,7 @@ if (!window.SequraFE) { * * @param {ElementProps & {text: string, href: string}} props */ - const createButtonLinkField = ({label, text, description, href, error}) => { + const createButtonLinkField = ({ label, text, description, href, error }) => { const buttonLink = createButtonLink({ text: translationService.translate(text), className: '', @@ -444,7 +444,7 @@ if (!window.SequraFE) { * @param {ElementProps & MultiItemSelectorComponentModel} props The properties. * @return {HTMLDivElement} */ - const createMultiItemSelectorField = ({label, description, variation, error, ...config}) => { + const createMultiItemSelectorField = ({ label, description, variation, error, ...config }) => { return createFieldWrapper( SequraFE.components.MultiItemSelector.create(config), label, @@ -481,7 +481,7 @@ if (!window.SequraFE) { messageBlock = createElement('span', 'sqp-alert-title', messageKey); } - const button = createButton({onClick: hideHandler}); + const button = createButton({ onClick: hideHandler }); if (clearAfter) { setTimeout(hideHandler, clearAfter); @@ -519,7 +519,7 @@ if (!window.SequraFE) { * @param {() => void} onCancel * @returns HTMLElement */ - const createPageFooter = ({onSave, onCancel}) => { + const createPageFooter = ({ onSave, onCancel }) => { return createElement('div', 'sq-page-footer', '', null, [ createElement('div', 'sqp-actions', '', null, [ createButton({ @@ -548,13 +548,13 @@ if (!window.SequraFE) { const createFormFields = (fields) => { /** @type HTMLElement[] */ const result = []; - fields.forEach(({type, ...rest}) => { + fields.forEach(({ type, ...rest }) => { switch (type) { case 'text': - result.push(createTextField({...rest, className: 'sq-text-input'})); + result.push(createTextField({ ...rest, className: 'sq-text-input' })); break; case 'number': - result.push(createNumberField({...rest, className: 'sq-text-input'})); + result.push(createNumberField({ ...rest, className: 'sq-text-input' })); break; case 'dropdown': result.push(createDropdownField(rest)); @@ -585,7 +585,7 @@ if (!window.SequraFE) { * @param {{title?: string, text?: string}} params * @returns {HTMLElement} */ - const createPageHeading = ({title, text}) => { + const createPageHeading = ({ title, text }) => { return createElement('div', 'sqp-page-heading', '', null, [ createElement('h3', 'sqp-page-title', title), createElement('span', 'sqp-description', text) @@ -598,7 +598,7 @@ if (!window.SequraFE) { * @param {{label: string, description?: string, href: string, isActive?: boolean, isCompleted?: boolean}[]} steps * @returns {HTMLElement} */ - const createWizardSidebar = ({steps}) => { + const createWizardSidebar = ({ steps }) => { const wrapper = createElement('div', 'sq-wizard-sidebar'); wrapper.append( @@ -627,7 +627,7 @@ if (!window.SequraFE) { * @param {{label: string, icon: string, href: string, isActive?: boolean}[]} links * @returns {HTMLElement} */ - const createSettingsSidebar = ({links}) => { + const createSettingsSidebar = ({ links }) => { const wrapper = createElement('ul', 'sq-settings-sidebar'); wrapper.append( @@ -649,6 +649,30 @@ if (!window.SequraFE) { return wrapper; }; + /** + * Creates a support link FAB. + * + * @returns {HTMLElement} + */ + const createSupportLink = () => { + return createElement( + 'a', + 'sq-support-link', + '', + { + href: SequraFE.translationService.translate('supportLink.link'), + target: '_blank' + }, + [ + createElement( + 'span', + 'sq-support-link-label', + 'supportLink.label' + ) + ] + ); + } + SequraFE.elementGenerator = { createElement, createElementFromHTML, @@ -675,6 +699,7 @@ if (!window.SequraFE) { createPageHeading, createVersionBadge, createWizardSidebar, - createSettingsSidebar + createSettingsSidebar, + createSupportLink }; })(); diff --git a/view/adminhtml/web/js/GeneralSettingsForm.js b/view/adminhtml/web/js/GeneralSettingsForm.js index c0835676..f84df0db 100644 --- a/view/adminhtml/web/js/GeneralSettingsForm.js +++ b/view/adminhtml/web/js/GeneralSettingsForm.js @@ -43,6 +43,7 @@ if (!window.SequraFE) { * }} data * @param {{ * saveGeneralSettingsUrl: string, + * getGeneralSettingsUrl: string, * saveCountrySettingsUrl: string, * validateConnectionDataUrl: string, * page: string, @@ -56,6 +57,13 @@ if (!window.SequraFE) { validationService: validator, utilities } = SequraFE; + + const classNameServiceRelatedField = 'sq-service-related-field'; + const classNameEnabledForService = 'sq-field-enabled-for-services'; + const classNameAllowFirstServicePaymentDelay = 'sq-field-allow-first-service-payment-delay'; + const classNameAllowServiceRegistrationItems = 'sq-field-allow-service-registration-items'; + const classNameDefaultServicesEndDate = 'sq-default-services-end-date'; + /** @type AjaxServiceType */ const api = SequraFE.ajaxService; /** @type GeneralSettings */ @@ -77,7 +85,11 @@ if (!window.SequraFE) { sendOrderReportsPeriodicallyToSeQura: false, allowedIPAddresses: [], excludedCategories: [], - excludedProducts: [] + excludedProducts: [], + enabledForServices: [], + allowFirstServicePaymentDelay: [], + allowServiceRegistrationItems: [], + defaultServicesEndDate: 'P1Y' }; /** @@ -142,13 +154,23 @@ if (!window.SequraFE) { } if (configuration.appState === SequraFE.appStates.SETTINGS && !SequraFE.isPromotional) { + const { + isShowCheckoutAsHostedPageFieldVisible, + isServiceSellingAllowed + } = SequraFE.flags; + + if (isShowCheckoutAsHostedPageFieldVisible) { + pageInnerContent?.append( + generator.createToggleField({ + value: changedGeneralSettings.showSeQuraCheckoutAsHostedPage, + label: 'generalSettings.showCheckoutAsHostedPage.label', + description: 'generalSettings.showCheckoutAsHostedPage.description', + onChange: (value) => handleGeneralSettingsChange('showSeQuraCheckoutAsHostedPage', value) + }) + ); + } + pageInnerContent?.append( - generator.createToggleField({ - value: changedGeneralSettings.showSeQuraCheckoutAsHostedPage, - label: 'generalSettings.showCheckoutAsHostedPage.label', - description: 'generalSettings.showCheckoutAsHostedPage.description', - onChange: (value) => handleGeneralSettingsChange('showSeQuraCheckoutAsHostedPage', value) - }), generator.createMultiItemSelectorField({ name: 'allowedIPAddresses-selector', label: 'generalSettings.allowedIPAddresses.label', @@ -161,7 +183,7 @@ if (!window.SequraFE) { label: 'generalSettings.excludedCategories.label', description: 'generalSettings.excludedCategories.description', value: changedGeneralSettings.excludedCategories?.join(','), - options: data.shopCategories.map((category) => ({label: category.name, value: category.id})), + options: data.shopCategories.map((category) => ({ label: category.name, value: category.id })), onChange: (value) => handleGeneralSettingsChange('excludedCategories', value) }), generator.createMultiItemSelectorField({ @@ -171,7 +193,39 @@ if (!window.SequraFE) { searchable: false, onChange: (value) => handleGeneralSettingsChange('excludedProducts', value) }) - ) + ); + + if (isServiceSellingAllowed) { + + pageInnerContent?.append( + generator.createToggleField({ + className: classNameEnabledForService, + disabled: true, + label: 'generalSettings.enabledForServices.label', + description: 'generalSettings.enabledForServices.description' + }), + generator.createToggleField({ + className: `${classNameServiceRelatedField} ${classNameAllowFirstServicePaymentDelay}`, + disabled: true, + label: 'generalSettings.allowFirstServicePaymentDelay.label', + description: 'generalSettings.allowFirstServicePaymentDelay.description' + }), + generator.createToggleField({ + className: `${classNameServiceRelatedField} ${classNameAllowServiceRegistrationItems}`, + disabled: true, + label: 'generalSettings.allowServiceRegistrationItems.label', + description: 'generalSettings.allowServiceRegistrationItems.description' + }), + generator.createTextField({ + className: `sq-text-input ${classNameServiceRelatedField} ${classNameDefaultServicesEndDate}`, + label: 'generalSettings.defaultServicesEndDate.label', + description: 'generalSettings.defaultServicesEndDate.description', + onChange: (value) => handleGeneralSettingsChange('defaultServicesEndDate', value) + }) + ); + + showOrHideServiceRelatedFields(); + } } pageInnerContent?.append( @@ -180,7 +234,7 @@ if (!window.SequraFE) { label: 'countries.selector.label', description: 'countries.selector.description', value: changedCountryConfiguration.map((country) => country.countryCode).join(','), - options: data.sellingCountries.map((country) => ({label: country.name, value: country.code})), + options: data.sellingCountries.map((country) => ({ label: country.name, value: country.code })), onChange: handleCountryChange }) ); @@ -322,24 +376,76 @@ if (!window.SequraFE) { disableFooter(false); } - /** - * Check if a given string is a valid IP address. - * - * @param {string} str - * - * @returns {boolean} - */ - const checkIfValidIP = (str) => { - const regexExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gi; + const showOrHideServiceRelatedFields = () => { + if (!SequraFE.flags.isServiceSellingAllowed) { + return; + } + // Update the values and descriptions of the fields. + const countriesString = countries => { + // input is like ['ES', 'PT'] + // output is like 'Spain and Portugal' or 'Spain, France, and Portugal' + let countriesString = ''; + const translate = SequraFE.translationService.translate; + for (let i = 0; i < countries.length; i++) { + const country = '' + translate('countries.' + countries[i] + '.label') + ''; + if (i === 0) { + countriesString += country; + } else if (i === countries.length - 1) { + countriesString += translate('general.and') + country; + } else { + countriesString += ', ' + country; + } + } + return countriesString ? translate('countries.enabledCountries').replace('{countries}', countriesString) : ''; + } + const descriptionWithCountries = (description, countries) => SequraFE.translationService.translate(description) + countriesString(countries); + + const enabledForServiceInput = document.querySelector(`.${classNameEnabledForService} input`); + if (enabledForServiceInput) { + enabledForServiceInput.checked = changedGeneralSettings.enabledForServices.length > 0; + } + const enabledForServiceSubtitle = document.querySelector(`.${classNameEnabledForService} .sqp-field-subtitle`); + if (enabledForServiceSubtitle) { + enabledForServiceSubtitle.innerHTML = descriptionWithCountries('generalSettings.enabledForServices.description', changedGeneralSettings.enabledForServices); + } + const allowFirstServicePaymentDelayInput = document.querySelector(`.${classNameAllowFirstServicePaymentDelay} input`); + if (allowFirstServicePaymentDelayInput) { + allowFirstServicePaymentDelayInput.checked = changedGeneralSettings.allowFirstServicePaymentDelay.length > 0; + } + const allowFirstServicePaymentDelaySubtitle = document.querySelector(`.${classNameAllowFirstServicePaymentDelay} .sqp-field-subtitle`); + if (allowFirstServicePaymentDelaySubtitle) { + allowFirstServicePaymentDelaySubtitle.innerHTML = descriptionWithCountries('generalSettings.allowFirstServicePaymentDelay.description', changedGeneralSettings.allowFirstServicePaymentDelay); + } + const allowServiceRegistrationItemsInput = document.querySelector(`.${classNameAllowServiceRegistrationItems} input`); + if (allowServiceRegistrationItemsInput) { + allowServiceRegistrationItemsInput.checked = changedGeneralSettings.allowServiceRegistrationItems.length > 0; + } + const allowServiceRegistrationItemsSubtitle = document.querySelector(`.${classNameAllowServiceRegistrationItems} .sqp-field-subtitle`); + if (allowServiceRegistrationItemsSubtitle) { + allowServiceRegistrationItemsSubtitle.innerHTML = descriptionWithCountries('generalSettings.allowServiceRegistrationItems.description', changedGeneralSettings.allowServiceRegistrationItems); + } + const defaultServicesEndDateInput = document.querySelector(`.${classNameDefaultServicesEndDate}`); + if (defaultServicesEndDateInput) { + defaultServicesEndDateInput.value = changedGeneralSettings.defaultServicesEndDate; + } - return regexExp.test(str); + // Update the visibility of the fields. + const selector = '.sq-field-wrapper:has(.sq-service-related-field), .sq-field-wrapper.sq-service-related-field' + const hiddenClass = 'sqs--hidden'; + document.querySelectorAll(selector).forEach((el) => { + if (changedGeneralSettings.enabledForServices.length > 0) { + el.classList.remove(hiddenClass) + } else { + el.classList.add(hiddenClass) + } + }); } const areIPAddressesValid = () => { let hasError = false; changedGeneralSettings.allowedIPAddresses.forEach((address) => { - if (!checkIfValidIP(address)) { + if (!validator.validateIpAddress(address)) { hasError = true; } }); @@ -353,11 +459,22 @@ if (!window.SequraFE) { return !hasError; } + const isValidTimeDuration = () => { + const valid = validator.validateDateOrDuration(changedGeneralSettings.defaultServicesEndDate); + validator.validateField( + document.querySelector('.sq-default-services-end-date'), + !valid, + 'validation.invalidTimeDuration' + ); + + return valid; + } + /** * Handles saving of the form. */ const handleSave = () => { - if (!isCountryConfigurationValid() || !areIPAddressesValid()) { + if (!isCountryConfigurationValid() || !areIPAddressesValid() || !isValidTimeDuration()) { return; } @@ -367,34 +484,6 @@ if (!window.SequraFE) { saveChangedData(); } - /** - * Handle merchant id validation error. - * - * @param {[{isValid: boolean, reason: string|null}]} results - */ - const handleValidationError = (results) => { - if (results[0].reason && !results[0].reason.includes('merchantId')) { - SequraFE.responseService.errorHandler( - {errorCode: 'general.errors.connection.invalidUsernameOrPassword'} - ).catch(() => { - }); - - utilities.hideLoader(); - - return; - } - - results.forEach((result, index) => { - validator.validateField( - document.querySelector(`[name="country_${changedCountryConfiguration[index].countryCode}"]`), - !result.isValid, - 'validation.invalidField' - ); - }); - - utilities.hideLoader(); - } - /** * Save changed data. */ @@ -404,19 +493,33 @@ if (!window.SequraFE) { const promises = []; haveGeneralSettingsChanged && - promises.push(api.post(configuration.saveGeneralSettingsUrl, changedGeneralSettings)); + promises.push(api.post(configuration.saveGeneralSettingsUrl, changedGeneralSettings, SequraFE.customHeader)); hasCountryConfigurationChanged && - promises.push(api.post(configuration.saveCountrySettingsUrl, changedCountryConfiguration)); + promises.push(api.post(configuration.saveCountrySettingsUrl, changedCountryConfiguration, SequraFE.customHeader)); Promise.all(promises) - .then(() => { + .then(async () => { disableFooter(true); - activeGeneralSettings = utilities.cloneObject(changedGeneralSettings); + if (configuration.appState === SequraFE.appStates.ONBOARDING) { + activeGeneralSettings = utilities.cloneObject(changedGeneralSettings); + } else { + try { + activeGeneralSettings = await api.get(configuration.getGeneralSettingsUrl, null, SequraFE.customHeader); + if (JSON.stringify(activeGeneralSettings) !== JSON.stringify(changedGeneralSettings)) { + changedGeneralSettings = utilities.cloneObject(activeGeneralSettings); + // Update the service components in the form. + showOrHideServiceRelatedFields(); + } + } catch (error) { + activeGeneralSettings = utilities.cloneObject(changedGeneralSettings); + SequraFE.responseService.errorHandler({ errorCode: 'general.errors.backgroundDataFetchFailure' }).catch(e => console.error(e)); + } + } activeCountryConfiguration = changedCountryConfiguration.map((utilities.cloneObject)) configuration.appState === SequraFE.appStates.SETTINGS && - SequraFE.state.setData('generalSettings', activeGeneralSettings); + SequraFE.state.setData('generalSettings', activeGeneralSettings); SequraFE.state.setData('countrySettings', activeCountryConfiguration); haveGeneralSettingsChanged = false; diff --git a/view/adminhtml/web/js/OnboardingController.js b/view/adminhtml/web/js/OnboardingController.js index 8923d489..d20cc614 100644 --- a/view/adminhtml/web/js/OnboardingController.js +++ b/view/adminhtml/web/js/OnboardingController.js @@ -79,24 +79,24 @@ if (!window.SequraFE) { case SequraFE.appPages.ONBOARDING.COUNTRIES: renderer = renderCountrySettingsForm; promises = Promise.all([ - SequraFE.state.getData('sellingCountries') ?? api.get(configuration.getSellingCountriesUrl) + SequraFE.state.getData('sellingCountries') ?? api.get(configuration.getSellingCountriesUrl, null, SequraFE.customHeader) ]) break; case SequraFE.appPages.ONBOARDING.WIDGETS: renderer = renderWidgetSettingsForm; promises = Promise.all([ SequraFE.state.getData('paymentMethods') ?? api.get(configuration.getPaymentMethodsUrl.replace( - encodeURIComponent('{merchantId}'), + '{merchantId}', countrySettings[0].merchantId - )), - SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl), + ), null, SequraFE.customHeader), + SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl, null, SequraFE.customHeader), ]) break; case SequraFE.appPages.ONBOARDING.DEPLOYMENTS: renderer = renderDeploymentsSettingForm; promises = Promise.all([ - SequraFE.state.getData('deploymentsSettings') ?? api.get(configuration.getDeploymentSettingsUrl) + SequraFE.state.getData('deploymentsSettings') ?? api.get(configuration.getDeploymentSettingsUrl, null, SequraFE.customHeader) ]); break; @@ -258,6 +258,7 @@ if (!window.SequraFE) { ]) : [], getSetupWizardRow() ]), + generator.createSupportLink() ]) ) } diff --git a/view/adminhtml/web/js/OrderStatusMappingSettingsForm.js b/view/adminhtml/web/js/OrderStatusMappingSettingsForm.js index 5df7ed15..a52da9a8 100644 --- a/view/adminhtml/web/js/OrderStatusMappingSettingsForm.js +++ b/view/adminhtml/web/js/OrderStatusMappingSettingsForm.js @@ -5,8 +5,8 @@ if (!window.SequraFE) { (function () { /** * @typedef ShopOrderStatus - * @property {string} statusId - * @property {string} statusName + * @property {string} id + * @property {string} name */ /** @@ -15,17 +15,11 @@ if (!window.SequraFE) { * @property {string} shopStatus */ - /** - * @typedef OrderStatusSettings - * @property {OrderStatusMapping[]} orderStatusMappings - * @property {boolean} informCancellationsToSequra - */ - /** * Handles order status mapping settings form logic. * * @param {{ - * orderStatusSettings: OrderStatusSettings, + * orderStatusSettings: OrderStatusMapping[], * shopOrderStatuses: ShopOrderStatus[], * shopName: string * }} data @@ -44,7 +38,7 @@ if (!window.SequraFE) { CANCELLED: 'cancelled' } - const { elementGenerator: generator, utilities} = SequraFE; + const { elementGenerator: generator, utilities } = SequraFE; /** @type AjaxServiceType */ const api = SequraFE.ajaxService; /** @type OrderStatusSettings */ @@ -64,14 +58,8 @@ if (!window.SequraFE) { this.render = () => { utilities.showLoader(); - if(!activeSettings) { - activeSettings = utilities.cloneObject(defaultFormData); - activeSettings.informCancellationsToSequra = - data?.orderStatusSettings?.informCancellationsToSequra ?? - defaultFormData.informCancellationsToSequra; - activeSettings.orderStatusMappings = - data?.orderStatusSettings?.orderStatusMappings ?? - defaultFormData.orderStatusMappings; + if (!activeSettings) { + activeSettings = data?.orderStatusSettings ? data.orderStatusSettings.map(utilities.cloneObject) : utilities.cloneObject(defaultFormData); } changedSettings = utilities.cloneObject(activeSettings); @@ -91,12 +79,14 @@ if (!window.SequraFE) { generator.createElement('div', 'sqp-flash-message-wrapper'), generator.createPageHeading({ title: 'orderStatusSettings.title', - text: 'orderStatusSettings.description' + text: SequraFE.translationService.translate( + 'orderStatusSettings.description' + ).replace('{shopName}', data.shopName), }), generator.createDropdownField({ label: 'orderStatusSettings.paid.label', description: 'orderStatusSettings.paid.description', - value: changedSettings?.orderStatusMappings?.find( + value: changedSettings.find( (mapping) => mapping.sequraStatus === SEQURA_STATUSES.PAID )?.shopStatus || '', options: getStatusOptions(), @@ -106,7 +96,7 @@ if (!window.SequraFE) { generator.createDropdownField({ label: 'orderStatusSettings.inReview.label', description: 'orderStatusSettings.inReview.description', - value: changedSettings?.orderStatusMappings?.find( + value: changedSettings.find( (mapping) => mapping.sequraStatus === SEQURA_STATUSES.IN_REVIEW )?.shopStatus, options: getStatusOptions(), @@ -116,21 +106,13 @@ if (!window.SequraFE) { generator.createDropdownField({ label: 'orderStatusSettings.cancelled.label', description: 'orderStatusSettings.cancelled.description', - value: changedSettings.orderStatusMappings?.find( + value: changedSettings.find( (mapping) => mapping.sequraStatus === SEQURA_STATUSES.CANCELLED )?.shopStatus, options: getStatusOptions(), variation: 'label-left', onChange: (value) => handleChange(SEQURA_STATUSES.CANCELLED, value) }), - generator.createToggleField({ - value: changedSettings.informCancellationsToSequra, - label: 'orderStatusSettings.informCancellations.label', - description: SequraFE.translationService.translate( - 'orderStatusSettings.informCancellations.description' - ).replace('{{shopName}}', data.shopName), - onChange: (value) => handleChange('informCancellationsToSequra', value) - }) ]), generator.createPageFooter({ onCancel: () => { @@ -152,11 +134,11 @@ if (!window.SequraFE) { * @returns {[{label: string, value: string}]} */ const getStatusOptions = () => { - const options = [{ label: "None", value: ""}]; + const options = [{ label: "None", value: "" }]; data.shopOrderStatuses.map((shopOrderStatus) => { options.push({ - label: shopOrderStatus.statusName.charAt(0).toUpperCase() + shopOrderStatus.statusName.slice(1), - value: shopOrderStatus.statusId + label: shopOrderStatus.name.charAt(0).toUpperCase() + shopOrderStatus.name.slice(1), + value: shopOrderStatus.id }) }); @@ -170,14 +152,10 @@ if (!window.SequraFE) { * @param value */ const handleChange = (name, value) => { - if(name === 'informCancellationsToSequra') { - changedSettings[name] = value; - } else { - const mapping = changedSettings.orderStatusMappings.find((mapping) => mapping.sequraStatus === name) - mapping ? - mapping.shopStatus = value : - changedSettings.orderStatusMappings.push({shopStatus: value, sequraStatus: name}); - } + const mapping = changedSettings.find((mapping) => mapping.sequraStatus === name) + mapping ? + mapping.shopStatus = value : + changedSettings.push({ shopStatus: value, sequraStatus: name }); utilities.disableFooter(false); } @@ -187,9 +165,10 @@ if (!window.SequraFE) { */ const handleSave = () => { utilities.showLoader(); - api.post(configuration.saveOrderStatusMappingSettingsUrl, changedSettings) + api.post(configuration.saveOrderStatusMappingSettingsUrl, changedSettings, SequraFE.customHeader) .then(() => { activeSettings = utilities.cloneObject(changedSettings); + SequraFE.state.setData('orderStatusSettings', activeSettings); utilities.disableFooter(true); }) .finally(utilities.hideLoader); diff --git a/view/adminhtml/web/js/PaymentController.js b/view/adminhtml/web/js/PaymentController.js index 463cec14..56f746ac 100644 --- a/view/adminhtml/web/js/PaymentController.js +++ b/view/adminhtml/web/js/PaymentController.js @@ -69,8 +69,8 @@ if (!window.SequraFE) { } Promise.all([ - sellingCountries ? [] : api.get(configuration.getSellingCountriesUrl), - paymentMethods ? [] : api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), countryConfiguration[0].merchantId)), + sellingCountries ? [] : api.get(configuration.getSellingCountriesUrl, null, SequraFE.customHeader), + paymentMethods ? [] : api.get(configuration.getPaymentMethodsUrl.replace('{merchantId}', countryConfiguration[0].merchantId), null, SequraFE.customHeader), ]).then(([sellingCountriesRes, paymentMethodsRes]) => { if (sellingCountriesRes.length !== 0) { sellingCountries = sellingCountriesRes; @@ -111,17 +111,7 @@ if (!window.SequraFE) { SequraFE.state.display(); } }, - menuItems: [ - { - label: 'general.paymentMethods', - href: window.location.href.split('#')[0] + '#payment', - isActive: true, - }, - { - label: 'general.settings', - href: window.location.href.split('#')[0] + '#settings' - } - ] + menuItems: SequraFE.utilities.getMenuItems(SequraFE.appStates.PAYMENT) } ), generator.createElement('div', 'sq-page-content', '', null, [ @@ -153,6 +143,7 @@ if (!window.SequraFE) { ]) ]) ]), + generator.createSupportLink() ])) } @@ -185,8 +176,9 @@ if (!window.SequraFE) { { className: 'sqp-payment-method-cell sqm--text-left', renderer: (cell) => { + const icon = method.icon ? method.icon : (SequraFE.imagesProvider.icons.payment || ''); cell.prepend( - generator.createElementFromHTML(SequraFE.imagesProvider.icons.payment || ''), + generator.createElementFromHTML(icon), generator.createElement('div', '', '', null, [ generator.createElement('h3', 'sqp-payment-method-title', method.title), generator.createElement( @@ -213,7 +205,7 @@ if (!window.SequraFE) { */ const handleCountryChange = (value) => { utilities.showLoader(); - api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), value)) + api.get(configuration.getPaymentMethodsUrl.replace('{merchantId}', value), null, SequraFE.customHeader) .then((methods) => { paymentMethods = [...methods]; document.querySelector('.sq-table-container').remove(); diff --git a/view/adminhtml/web/js/SettingsController.js b/view/adminhtml/web/js/SettingsController.js index 8ea57a03..c3ae175c 100644 --- a/view/adminhtml/web/js/SettingsController.js +++ b/view/adminhtml/web/js/SettingsController.js @@ -78,34 +78,36 @@ if (!window.SequraFE) { promises = Promise.all([ SequraFE.state.getData('notConnectedDeployments') ?? api.get( configuration.pageConfiguration.onboarding.getNotConnectedDeploymentsUrl.replace( - encodeURIComponent('{storeId}'), SequraFE.state.getStoreId() - ) + '{storeId}', SequraFE.state.getStoreId() + ), + null, + SequraFE.customHeader ), ]) break; case SequraFE.appPages.SETTINGS.ORDER_STATUS: renderer = renderOrderStatusMappingSettingsForm; promises = Promise.all([ - api.get(configuration.getOrderStatusMappingSettingsUrl), - api.get(configuration.getShopOrderStatusesUrl), + api.get(configuration.getOrderStatusMappingSettingsUrl, null, SequraFE.customHeader), + api.get(configuration.getShopOrderStatusesUrl, null, SequraFE.customHeader), SequraFE.state.getShopName() ]) break; case SequraFE.appPages.SETTINGS.WIDGET: renderer = renderWidgetSettingsForm; promises = Promise.all([ - SequraFE.state.getData('paymentMethods') ?? api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), countrySettings[0].merchantId)), - SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl), + SequraFE.state.getData('paymentMethods') ?? api.get(configuration.getPaymentMethodsUrl.replace('{merchantId}', countrySettings[0].merchantId), null, SequraFE.customHeader), + SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl, null, SequraFE.customHeader), ]) break; default: renderer = renderGeneralSettingsForm; promises = Promise.all([ SequraFE.isPromotional ? [] : - SequraFE.state.getData('generalSettings') ?? api.get(configuration.getGeneralSettingsUrl), + SequraFE.state.getData('generalSettings') ?? api.get(configuration.getGeneralSettingsUrl, null, SequraFE.customHeader), SequraFE.isPromotional ? [] : - SequraFE.state.getData('shopCategories') ?? api.get(configuration.getShopCategoriesUrl), - SequraFE.state.getData('sellingCountries') ?? api.get(configuration.getSellingCountriesUrl), + SequraFE.state.getData('shopCategories') ?? api.get(configuration.getShopCategoriesUrl, null, SequraFE.customHeader), + SequraFE.state.getData('sellingCountries') ?? api.get(configuration.getSellingCountriesUrl, null, SequraFE.customHeader), ]) } @@ -287,20 +289,11 @@ if (!window.SequraFE) { SequraFE.state.display(); } }, - menuItems: SequraFE.isPromotional ? [] : [ - { - label: 'general.paymentMethods', - href: window.location.href.split('#')[0] + '#payment' - }, - { - label: 'general.settings', - href: window.location.href.split('#')[0] + '#settings', - isActive: true, - } - ] + menuItems: SequraFE.utilities.getMenuItems(SequraFE.appStates.SETTINGS) } ), generator.createElement('div', 'sq-page-content', '', null, [getSidebarRow()]), + generator.createSupportLink() ])) } diff --git a/view/adminhtml/web/js/StateController.js b/view/adminhtml/web/js/StateController.js index e2e61c9a..6350f13d 100644 --- a/view/adminhtml/web/js/StateController.js +++ b/view/adminhtml/web/js/StateController.js @@ -2,10 +2,19 @@ if (!window.SequraFE) { window.SequraFE = {}; } +// Define the supported configuration capabilities. +SequraFE.flags = { + isShowCheckoutAsHostedPageFieldVisible: true, + configurableSelectorsForMiniWidgets: false, + isServiceSellingAllowed: false, + ...(SequraFE.flags || {}) +} + SequraFE.appStates = { ONBOARDING: 'onboarding', SETTINGS: 'settings', PAYMENT: 'payment', + ADVANCED: 'advanced' }; SequraFE.appPages = { @@ -23,6 +32,9 @@ SequraFE.appPages = { }, PAYMENT: { METHODS: 'methods' + }, + ADVANCED: { + DEBUG: 'debug' } }; @@ -69,6 +81,7 @@ SequraFE.appPages = { * @property {SellingCountry[] | null} sellingCountries * @property {DeploymentSettings[] | null} deploymentsSettings * @property {DeploymentSettings[] | null} notConnectedDeployments + * @property {LogsSettings | null} logsSettings * @property {Category[] | null} shopCategories */ @@ -79,6 +92,12 @@ SequraFE.appPages = { * @property {boolean} [active] */ + /** + * @typedef {Object} LogsSettings + * @property {boolean} enabled + * @property {int} level + */ + /** * Main controller of the application. * @@ -89,7 +108,7 @@ SequraFE.appPages = { function StateController(configuration) { /** @type AjaxServiceType */ const api = SequraFE.ajaxService; - const {pageControllerFactory, templateService, utilities} = SequraFE; + const { pageControllerFactory, templateService, utilities } = SequraFE; let currentState = ''; let previousState = ''; @@ -97,19 +116,26 @@ SequraFE.appPages = { /** * @type {DataStore} */ - let dataStore = { - version: null, - stores: null, - connectionSettings: null, - countrySettings: null, - deploymentsSettings: null, - notConnectedDeployments: null, - generalSettings: null, - widgetSettings: null, - paymentMethods: null, - sellingCountries: null, - shopCategories: null - }; + let dataStore; + + const clearDataStore = () => { + dataStore = { + version: null, + stores: null, + connectionSettings: null, + notConnectedDeployments: null, + deploymentsSettings: null, + countrySettings: null, + generalSettings: null, + widgetSettings: null, + paymentMethods: null, + sellingCountries: null, + shopCategories: null, + logsSettings: null + }; + } + + clearDataStore(); /** * Main entry point for the application. @@ -122,7 +148,7 @@ SequraFE.appPages = { window.addEventListener('hashchange', updateStateOnHashChange, false); - api.get(!this.getStoreId() ? configuration.currentStoreUrl : configuration.storesUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), () => null, true) + api.get(!this.getStoreId() ? configuration.currentStoreUrl : configuration.storesUrl.replace('{storeId}', this.getStoreId()), () => null, SequraFE.customHeader) .then( /** @param {Store|Store[]} response */ (response) => { @@ -138,7 +164,7 @@ SequraFE.appPages = { if (!store) { // the active store is probably deleted, we need to switch to the default store - return api.get(configuration.currentStoreUrl, null, true).then(loadStore); + return api.get(configuration.currentStoreUrl, null, SequraFE.customHeader).then(loadStore); } return loadStore(store); @@ -161,14 +187,15 @@ SequraFE.appPages = { utilities.showLoader(); return Promise.all([ - api.get(configuration.versionUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.storesUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.pageConfiguration.onboarding.getConnectionDataUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.pageConfiguration.onboarding.getCountrySettingsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.pageConfiguration.onboarding.getWidgetSettingsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.pageConfiguration.onboarding.getDeploymentsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - api.get(configuration.pageConfiguration.onboarding.getNotConnectedDeploymentsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())), - ]).then(([versionRes, storesRes, connectionSettingsRes, countrySettingsRes, widgetSettingsRes, deploymentsSettingsRes, notConnectedDeployments]) => { + api.get(configuration.versionUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.storesUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getConnectionDataUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getCountrySettingsUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getWidgetSettingsUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getDeploymentsUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getNotConnectedDeploymentsUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.advanced.getLogsSettingsUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader), + ]).then(([versionRes, storesRes, connectionSettingsRes, countrySettingsRes, widgetSettingsRes, deploymentsSettingsRes, notConnectedDeployments, logsSettingsRes]) => { dataStore.version = versionRes; dataStore.stores = storesRes; dataStore.connectionSettings = connectionSettingsRes; @@ -176,17 +203,23 @@ SequraFE.appPages = { dataStore.widgetSettings = widgetSettingsRes; dataStore.deploymentsSettings = deploymentsSettingsRes; dataStore.notConnectedDeployments = notConnectedDeployments; + dataStore.logsSettings = logsSettingsRes; - return api.get(configuration.stateUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId())); + return api.get(configuration.stateUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader); }).then((stateRes) => { if (SequraFE.state.getCredentialsChanged()) { SequraFE.state.removeCredentialsChanged(); } - let page = this.getPage(); + const page = this.getPage(); if (stateRes.state === SequraFE.appStates.ONBOARDING) { - this.goToState(SequraFE.appStates.ONBOARDING, null, true); + this.goToState(SequraFE.appStates.ONBOARDING + '-' + page, null, true); + + return; + } + if (SequraFE.pages?.advanced?.includes(page)) { + this.goToState(SequraFE.appStates.ADVANCED + '-' + page, null, true) return; } @@ -217,10 +250,17 @@ SequraFE.appPages = { let [controllerName, page] = state.split('-'); if (controllerName === SequraFE.appStates.ONBOARDING) { + + // To skip Widgets Onboarding we need to make sure that Widgets are configured (styles set and at least one display option enabled) + const areWidgetsConfigured = dataStore.widgetSettings?.widgetStyles !== undefined + && (dataStore.widgetSettings?.displayWidgetOnProductPage + || dataStore.widgetSettings?.showInstallmentAmountInCartPage + || dataStore.widgetSettings?.showInstallmentAmountInProductListing + ); if ( dataStore.connectionSettings?.connectionData?.every(c => c.username && c.password) && dataStore.countrySettings?.length && - dataStore.widgetSettings?.widgetStyles !== undefined && + areWidgetsConfigured && !SequraFE.state.getCredentialsChanged() ) { currentState.split('-')[0] === SequraFE.appStates.ONBOARDING ? @@ -231,7 +271,7 @@ SequraFE.appPages = { } if (!page) { - page = SequraFE.appPages.ONBOARDING.COUNTRIES; + page = SequraFE.appPages.ONBOARDING.CONNECT; } switch (page) { @@ -275,14 +315,16 @@ SequraFE.appPages = { return; } - if ( - !dataStore.connectionSettings?.connectionData?.every(c => c.username && c.password) || - dataStore.countrySettings?.length === 0 || - dataStore.widgetSettings?.widgetStyles === undefined || - SequraFE.state.getCredentialsChanged() - ) { - this.goToState(SequraFE.appStates.ONBOARDING, additionalConfig, true); - + if (!dataStore.connectionSettings?.connectionData?.every(c => c.username && c.password) || SequraFE.state.getCredentialsChanged()) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.CONNECT, additionalConfig, true); + return; + } + if (dataStore.countrySettings?.length === 0) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.COUNTRIES, additionalConfig, true); + return; + } + if ('undefined' === typeof dataStore.widgetSettings?.widgetStyles) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.WIDGETS, additionalConfig, true); return; } @@ -300,7 +342,7 @@ SequraFE.appPages = { state = page ? controllerName + '-' + page : controllerName; } - const config = {storeId: this.getStoreId(), ...(additionalConfig || {})}; + const config = { storeId: this.getStoreId(), ...(additionalConfig || {}) }; const controller = pageControllerFactory.getInstance( controllerName, getControllerConfiguration(controllerName, page) @@ -325,7 +367,7 @@ SequraFE.appPages = { const getControllerConfiguration = (controllerName, page) => { let config = utilities.cloneObject(configuration.pageConfiguration[controllerName] || {}); Object.keys(config).forEach((key) => { - config[key] = config[key].replace(encodeURIComponent('{storeId}'), this.getStoreId); + config[key] = config[key].replace('{storeId}', encodeURIComponent(this.getStoreId())); }); page && (config.page = page); @@ -411,8 +453,7 @@ SequraFE.appPages = { * @returns {Promise} */ this.getShopName = () => { - return api.get(configuration.shopNameUrl, () => { - }); + return api.get(configuration.shopNameUrl.replace('{storeId}', this.getStoreId()), null, SequraFE.customHeader); }; this.getData = (key) => { @@ -428,22 +469,6 @@ SequraFE.appPages = { dataStore[key] = value; } } - - const clearDataStore = () => { - dataStore = { - version: null, - stores: null, - connectionSettings: null, - notConnectedDeployments: null, - deploymentsSettings: null, - countrySettings: null, - generalSettings: null, - widgetSettings: null, - paymentMethods: null, - sellingCountries: null, - shopCategories: null - }; - } } SequraFE.StateController = StateController; diff --git a/view/adminhtml/web/js/TranslationService.js b/view/adminhtml/web/js/TranslationService.js index 16db4c92..7db163cd 100644 --- a/view/adminhtml/web/js/TranslationService.js +++ b/view/adminhtml/web/js/TranslationService.js @@ -35,7 +35,7 @@ if (!window.SequraFE) { * @returns {null|string} */ const getTranslation = (type, group, key) => { - if (SequraFE.translations[type][group] && SequraFE.translations[type][group]) { + if (SequraFE.translations[type][group]) { let value = SequraFE.translations[type][group]; if (Array.isArray(key)) { return key.reduce((value, key) => { diff --git a/view/adminhtml/web/js/UtilityService.js b/view/adminhtml/web/js/UtilityService.js index 97017539..36112891 100644 --- a/view/adminhtml/web/js/UtilityService.js +++ b/view/adminhtml/web/js/UtilityService.js @@ -58,7 +58,7 @@ if (!window.SequraFE) { const saveButton = document.querySelector('.sq-page-footer .sqp-actions .sqp-save'); const cancelButton = document.querySelector('.sq-page-footer .sqp-actions .sqp-cancel'); - if(saveButton && cancelButton){ + if (saveButton && cancelButton) { saveButton.disabled = disable; cancelButton.disabled = disable; } @@ -94,6 +94,32 @@ if (!window.SequraFE) { return parent; }; + + /** + * Returns the menu item array for page navigation. + * + * @param {string} activePage + * @return {Array<{label: string, href: string, isActive: boolean}>} + */ + this.getMenuItems = (activePage) => { + return SequraFE.isPromotional ? [] : [ + { + label: 'general.paymentMethods', + href: window.location.href.split('#')[0] + '#payment', + isActive: activePage === SequraFE.appStates.PAYMENT + }, + { + label: 'general.settings', + href: window.location.href.split('#')[0] + '#settings', + isActive: activePage === SequraFE.appStates.SETTINGS + }, + { + label: 'general.advanced', + href: window.location.href.split('#')[0] + '#advanced', + isActive: activePage === SequraFE.appStates.ADVANCED + } + ]; + }; } SequraFE.utilities = new UtilityService(); diff --git a/view/adminhtml/web/js/ValidationService.js b/view/adminhtml/web/js/ValidationService.js index 70bd3854..5d6fa3e9 100644 --- a/view/adminhtml/web/js/ValidationService.js +++ b/view/adminhtml/web/js/ValidationService.js @@ -2,6 +2,10 @@ if (!window.SequraFE) { window.SequraFE = {}; } +if (typeof SequraFE.regex === 'undefined'){ + throw new Error('SequraFE.regex is not defined. Please provide the regex definitions before loading the ValidationService.'); +} + (function () { /** * @typedef ValidationMessage @@ -112,9 +116,7 @@ if (!window.SequraFE) { * @return {boolean} */ const validateEmail = (input, message) => { - let regex = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - + const regex = new RegExp(SequraFE.regex.email); return validateField(input, !regex.test(String(input.value).toLowerCase()), message); }; @@ -126,8 +128,7 @@ if (!window.SequraFE) { * @return {boolean} */ const validateUrl = (input, message) => { - let regex = /(https?:\/\/)([\w\-])+\.([a-zA-Z]{2,63})([\/\w-]*)*\/?\??([^#\n\r]*)?#?([^\n\r]*)/m; - + const regex = new RegExp(SequraFE.regex.url); return validateField(input, !regex.test(String(input.value).toLowerCase()), message); }; @@ -220,6 +221,29 @@ if (!window.SequraFE) { return validateField(input, !isValid, message); }; + /** + * Validates if the value is a valid date or duration following ISO 8601 format. + * + * @param {string} str + * @return {boolean} + */ + const validateDateOrDuration = (str) => { + const regex = new RegExp(SequraFE.regex.dateOrDuration); + return regex.test(str) && 'P' !== str && !str.endsWith('T'); + }; + + /** + * Check if a given string is a valid IP address. + * + * @param {string} str + * + * @returns {boolean} + */ + const validateIpAddress = (str) => { + const regex = new RegExp(SequraFE.regex.ip); + return regex.test(str); + }; + /** * Validates the provided JSON string and marks field invalid if the JSON is invalid. * @@ -286,7 +310,7 @@ if (!window.SequraFE) { 'validation.invalidJSON' ) && isValid; - isPaymentMethodValid = allowedPaymentMethods.some(pm => pm.product === location.product) + let isPaymentMethodValid = allowedPaymentMethods.some(pm => pm.product === location.product) && value.filter(l => l.product === location.product).length === 1; isValid = validateField( @@ -339,6 +363,8 @@ if (!window.SequraFE) { validateCustomLocations, validateField, validateRequiredField, + validateDateOrDuration, + validateIpAddress, handleValidationErrors }; })(); diff --git a/view/adminhtml/web/js/WidgetSettingsForm.js b/view/adminhtml/web/js/WidgetSettingsForm.js index 9ea5d4f8..60834315 100644 --- a/view/adminhtml/web/js/WidgetSettingsForm.js +++ b/view/adminhtml/web/js/WidgetSettingsForm.js @@ -60,8 +60,7 @@ if (!window.SequraFE) { * getPaymentMethodsUrl: string, * getAllPaymentMethodsUrl: string, * page: string, - * appState: string, - * configurableSelectorsForMiniWidgets: string + * appState: string * }} configuration * @constructor */ @@ -75,8 +74,7 @@ if (!window.SequraFE) { utilities } = SequraFE; - const configurableSelectorsForMiniWidgets = - configuration.configurableSelectorsForMiniWidgets === "true"; + const { configurableSelectorsForMiniWidgets } = SequraFE.flags; /** @type WidgetSettings */ let activeSettings; @@ -399,12 +397,12 @@ if (!window.SequraFE) { ${SequraFE.translationService.translate('widgets.displayOnProductPage.description')} -
+
${SequraFE.translationService.translate('widgets.locations.leaveEmptyToUseDefault')}
-
+
${SequraFE.translationService.translate('widgets.configurator.description.start')}${SequraFE.translationService.translate('widgets.configurator.description.link')}${SequraFE.translationService.translate('widgets.configurator.description.end')} ${SequraFE.translationService.translate('widgets.locations.leaveEmptyToUseDefault')} @@ -523,7 +521,7 @@ if (!window.SequraFE) { } utilities.showLoader(); - api.post(configuration.saveWidgetSettingsUrl, changedSettings) + api.post(configuration.saveWidgetSettingsUrl, changedSettings, SequraFE.customHeader) .then(() => { if (configuration.appState === SequraFE.appStates.ONBOARDING) { const index = SequraFE.pages.onboarding.indexOf(SequraFE.appPages.ONBOARDING.WIDGETS) diff --git a/view/adminhtml/web/lang/en.json b/view/adminhtml/web/lang/en.json index 109c5a70..257ed2d7 100644 --- a/view/adminhtml/web/lang/en.json +++ b/view/adminhtml/web/lang/en.json @@ -14,6 +14,7 @@ "merchant": "Merchant:", "settings": "Settings", "paymentMethods": "Payment methods", + "advanced": "Advanced", "changesSaved": "Changes saved successfully.", "noItemsAvailable": "No items available", "and": " and ", @@ -24,6 +25,7 @@ "errors": { "InvalidOperationException": "An error occurred on the server side. Please check the data and try again.", "unknown": "An unknown error occurred. Please try again later.", + "backgroundDataFetchFailure": "An error occurred while refreshing data in the background. Please reload the page to see the latest data.", "empty": "Parameter can't be empty.", "saveEntityFailed": "Failed to save entity because %s", "getEntityFailed": "Failed to retrieve entity because %s", @@ -82,7 +84,9 @@ "merchantError": "Merchant with id %s not found.", "failedToRegisterWebhook": "Failed to register webhook.", "hmacError": "Error generating HMAC." - } + }, + "failedToRetrieveLog": "Failed to retrieve log content.", + "failedToRemoveLog": "Failed to remove log content." } }, "sidebar": { @@ -114,7 +118,8 @@ "invalidFieldValue": "Field must be set to \"%s\".", "invalidMaxValue": "Maximal allowed value is %s.", "invalidImageType": "Please provide a file in one of the supported image types (PNG, JPG, SVG).", - "invalidImageSize": "The selected file exceeds the limit of 10MB." + "invalidImageSize": "The selected file exceeds the limit of 10MB.", + "invalidTimeDuration": "This field must contain only dates as 2017-08-31 or time duration as P3M15D (3 months and 15 days). Check ISO 8601." }, "payments": { "title": "Payment methods", @@ -129,6 +134,24 @@ "availableFrom": "Available from", "availableTo": "Available to" }, + "debug": { + "title": "Enable logs", + "description": "Help to diagnose issues with the seQura integration", + "noLogs": "There are no logs available", + "log": { + "severity": "Severity", + "message": "Message", + "datetime": "Date and time", + "removeModal": { + "title": "Delete logs", + "message": "Are you sure you want to clear the logs? You won't be able to recover them." + }, + "minLevel": "Minimum severity level", + "minLevelDescription": "Set the minimum severity level of logs to write to the log file." + }, + "reload": "Reload", + "remove": "Remove" + }, "connection": { "title": { "onboarding": "Getting started", @@ -210,7 +233,8 @@ "PE": { "label": "Peru", "description": "Please enter SeQura merchant ID that should be used for Peruvian customers." - } + }, + "enabledCountries": "Enabled for {countries}." }, "widgets": { "title": { @@ -295,7 +319,7 @@ }, "orderStatusSettings": { "title": "Order status settings", - "description": "Order status Set up your seQura account and choose the environment to start processing payments. ", + "description": "Define how seQura statuses map to {shopName} order statuses. The default values should work for most cases, so change them with caution.", "paid": { "label": "Paid", "description": "When a payment has status Paid." @@ -335,6 +359,26 @@ "excludedProducts": { "label": "Excluded products", "description": "The store products in this field will not be allowed to be purchased with SeQura payment methods. Please enter a valid SKU for every product you would like to exclude." + }, + "enabledForServices": { + "label": "Enabled for services", + "description": "Your contract must allow selling services. " + }, + "allowFirstServicePaymentDelay": { + "label": "Allow first service payment delay", + "description": "" + }, + "allowServiceRegistrationItems": { + "label": "Allow registration items", + "description": "Allows configuring part of the product price to be paid in advance as registration. " + }, + "defaultServicesEndDate": { + "label": "Default services end date", + "description": "Dates as 2017-08-31, time duration as P3M15D (3 months and 15 days). The default value for all products." } + }, + "supportLink": { + "label": "Need help?", + "link": "https://sqra.es/supportes" } } diff --git a/view/adminhtml/web/lang/es.json b/view/adminhtml/web/lang/es.json index 55fdcd8f..98c8df02 100644 --- a/view/adminhtml/web/lang/es.json +++ b/view/adminhtml/web/lang/es.json @@ -14,6 +14,7 @@ "merchant": "Comerciante:", "settings": "Ajustes", "paymentMethods": "Métodos de pago", + "advanced": "Avanzado", "changesSaved": "Cambios guardados exitosamente.", "noItemsAvailable": "No hay elementos disponibles", "mode": { @@ -23,6 +24,7 @@ "errors": { "InvalidOperationException": "Se produjo un error en el servidor. Por favor, verifica los datos e inténtalo de nuevo.", "unknown": "Se produjo un error desconocido. Por favor, inténtalo de nuevo más tarde.", + "backgroundDataFetchFailure": "Se produjo un error al actualizar los datos en segundo plano. Por favor, recarga la página para ver los últimos datos.", "empty": "El parámetro no puede estar vacío.", "saveEntityFailed": "Error al guardar la entidad porque %s", "getEntityFailed": "Error al recuperar la entidad porque %s", @@ -81,7 +83,9 @@ "merchantError": "Comerciante con ID %s no encontrado.", "failedToRegisterWebhook": "Error al registrar el webhook.", "hmacError": "Error al generar HMAC." - } + }, + "failedToRetrieveLog": "Fallo al recuperar el contenido del registro.", + "failedToRemoveLog": "Fallo al eliminar el contenido del registro." } }, "sidebar": { @@ -113,7 +117,8 @@ "invalidFieldValue": "El campo debe estar configurado en \"%s\".", "invalidMaxValue": "El valor máximo permitido es %s.", "invalidImageType": "Por favor, proporciona un archivo en uno de los tipos de imagen admitidos (PNG, JPG, SVG).", - "invalidImageSize": "El archivo seleccionado supera el límite de 10 MB." + "invalidImageSize": "El archivo seleccionado supera el límite de 10 MB.", + "invalidTimeDuration": "Este campo debe contener fechas como 2017-08-31 o una duración como P3M15D (3 meses y 15 días). Revisa el formato ISO 8601." }, "payments": { "title": "Métodos de pago", @@ -128,6 +133,24 @@ "availableFrom": "Disponible desde", "availableTo": "Disponible hasta" }, + "debug": { + "title": "Habilitar registros", + "description": "Ayuda a diagnosticar problemas con la integración de SeQura", + "noLogs": "No hay registros disponibles", + "log": { + "severity": "Gravedad", + "message": "Mensaje", + "datetime": "Fecha y hora", + "removeModal": { + "title": "Eliminar registros", + "message": "¿Estás seguro de que deseas borrar los registros? No podrás recuperarlos." + }, + "minLevel": "Nivel de gravedad mínimo", + "minLevelDescription": "Configura el nivel de gravedad mínimo de los registros que se escribirán en el archivo de registro." + }, + "reload": "Recargar", + "remove": "Eliminar" + }, "connection": { "title": { "onboarding": "Empezar", @@ -209,7 +232,8 @@ "PE": { "label": "Perú", "description": "Por favor, introduce la ID de comerciante de SeQura que se debe utilizar para los clientes peruanos." - } + }, + "enabledCountries": "Habilitado para {countries}." }, "widgets": { "title": { @@ -294,7 +318,7 @@ }, "orderStatusSettings": { "title": "Configuración de estados de pedido", - "description": "Descripción de la configuración de estados de pedido", + "description": "Define cómo los estados de seQura se asignan a los estados de pedido de {shopName}. Los valores predeterminados deberían funcionar en la mayoría de los casos, así que cámbialos con precaución.", "paid": { "label": "Pagado", "description": "Cuando un pago tiene el estado de Pagado." @@ -334,6 +358,26 @@ "excludedProducts": { "label": "Productos excluidos", "description": "Los productos de la tienda en este campo no podrán ser adquiridos con los métodos de pago de SeQura. Por favor, introduce un SKU válido para cada producto que desees excluir." + }, + "enabledForServices": { + "label": "Habilitado para servicios", + "description": "Tu contrato debe permitir la venta de servicios. " + }, + "allowFirstServicePaymentDelay": { + "label": "Permitir retraso en el primer pago del servicio", + "description": "" + }, + "allowServiceRegistrationItems": { + "label": "Permitir artículos de registro", + "description": "Permite configurar una parte del precio del producto para que se pague por adelantado como registro. " + }, + "defaultServicesEndDate": { + "label": "Fecha de finalización de servicios predeterminada", + "description": "Fechas como 2017-08-31, duración como P3M15D (3 meses y 15 días). El valor predeterminado para todos los productos." } + }, + "supportLink": { + "label": "¿Necesitas ayuda?", + "link": "https://sqra.es/supportes" } -} +} \ No newline at end of file diff --git a/view/adminhtml/web/lang/fr.json b/view/adminhtml/web/lang/fr.json index 6c37fc19..3436732f 100644 --- a/view/adminhtml/web/lang/fr.json +++ b/view/adminhtml/web/lang/fr.json @@ -14,6 +14,7 @@ "merchant": "Commerçant :", "settings": "Paramètres", "paymentMethods": "Méthodes de paiement", + "advanced": "Avancé", "changesSaved": "Modifications enregistrées avec succès.", "noItemsAvailable": "Aucun élément disponible", "and": " et ", @@ -24,6 +25,7 @@ "errors": { "InvalidOperationException": "Une erreur s'est produite côté serveur. Veuillez vérifier les données et réessayer.", "unknown": "Une erreur inconnue s'est produite. Veuillez réessayer plus tard.", + "backgroundDataFetchFailure": "Une erreur s'est produite lors de l'actualisation des données en arrière-plan. Veuillez recharger la page pour voir les dernières données.", "empty": "Le paramètre ne peut pas être vide.", "saveEntityFailed": "Échec de l'enregistrement de l'entité car %s", "getEntityFailed": "Échec de la récupération de l'entité car %s", @@ -82,7 +84,9 @@ "merchantError": "Commerçant avec l'ID %s introuvable.", "failedToRegisterWebhook": "Échec de l'enregistrement du webhook.", "hmacError": "Erreur lors de la génération du HMAC." - } + }, + "failedToRetrieveLog": "Échec de la récupération du contenu du journal.", + "failedToRemoveLog": "Échec de la suppression du contenu du journal." } }, "sidebar": { @@ -114,7 +118,8 @@ "invalidFieldValue": "Le champ doit être défini sur \"%s\".", "invalidMaxValue": "La valeur maximale autorisée est %s.", "invalidImageType": "Veuillez fournir un fichier dans l'un des types d'image pris en charge (PNG, JPG, SVG).", - "invalidImageSize": "Le fichier sélectionné dépasse la limite de 10 Mo." + "invalidImageSize": "Le fichier sélectionné dépasse la limite de 10 Mo.", + "invalidTimeDuration": "Ce champ doit contenir uniquement des dates au format 2017-08-31 ou des durées comme P3M15D (3 mois et 15 jours). Vérifiez la norme ISO 8601." }, "payments": { "title": "Méthodes de paiement", @@ -129,6 +134,24 @@ "availableFrom": "Disponible à partir de", "availableTo": "Disponible jusqu'à" }, + "debug": { + "title": "Activer les journaux", + "description": "Aide à diagnostiquer les problèmes avec l'intégration SeQura", + "noLogs": "Aucun journal disponible", + "log": { + "severity": "Gravité", + "message": "Message", + "datetime": "Date et heure", + "removeModal": { + "title": "Supprimer les journaux", + "message": "Es-tu sûr de vouloir effacer les journaux ? Tu ne pourras pas les récupérer." + }, + "minLevel": "Niveau de gravité minimal", + "minLevelDescription": "Définir le niveau de gravité minimal des journaux à écrire dans le fichier de journalisation." + }, + "reload": "Recharger", + "remove": "Supprimer" + }, "connection": { "title": { "onboarding": "Commencer", @@ -210,7 +233,8 @@ "PE": { "label": "Pérou", "description": "Veuillez saisir l'ID commerçant SeQura à utiliser pour les clients péruviens." - } + }, + "enabledCountries": "Activé pour {countries}." }, "widgets": { "title": { @@ -295,7 +319,7 @@ }, "orderStatusSettings": { "title": "Paramètres du statut de commande", - "description": "Configurez votre compte SeQura et choisissez l'environnement pour commencer à traiter les paiements.", + "description": "Définissez comment les statuts seQura sont associés aux statuts de commande de {shopName}. Les valeurs par défaut devraient convenir dans la plupart des cas, modifiez-les donc avec précaution.", "paid": { "label": "Payé", "description": "Lorsqu'un paiement a le statut Payé." @@ -335,6 +359,26 @@ "excludedProducts": { "label": "Produits exclus", "description": "Les produits de la boutique dans ce champ ne pourront pas être achetés avec les méthodes de paiement SeQura. Veuillez saisir un SKU valide pour chaque produit que vous souhaitez exclure." + }, + "enabledForServices": { + "label": "Activé pour les services", + "description": "Votre contrat doit permettre la vente de services. " + }, + "allowFirstServicePaymentDelay": { + "label": "Autoriser le délai du premier paiement de service", + "description": "" + }, + "allowServiceRegistrationItems": { + "label": "Autoriser les articles d'inscription", + "description": "Permet de configurer une partie du prix du produit à payer à l'avance en tant qu'inscription. " + }, + "defaultServicesEndDate": { + "label": "Date de fin des services par défaut", + "description": "Dates au format 2017-08-31, durée au format P3M15D (3 mois et 15 jours). Valeur par défaut pour tous les produits." } + }, + "supportLink": { + "label": "Besoin d'aide ?", + "link": "https://sqra.es/supportfr" } -} +} \ No newline at end of file diff --git a/view/adminhtml/web/lang/it.json b/view/adminhtml/web/lang/it.json index 876380f3..9056530b 100644 --- a/view/adminhtml/web/lang/it.json +++ b/view/adminhtml/web/lang/it.json @@ -14,6 +14,7 @@ "merchant": "Commerciante:", "settings": "Impostazioni", "paymentMethods": "Metodi di pagamento", + "advanced": "Avanzato", "changesSaved": "Modifiche salvate con successo.", "noItemsAvailable": "Nessun elemento disponibile", "and": " e ", @@ -24,6 +25,7 @@ "errors": { "InvalidOperationException": "Si è verificato un errore lato server. Controlla i dati e riprova.", "unknown": "Si è verificato un errore sconosciuto. Riprova più tardi.", + "backgroundDataFetchFailure": "Si è verificato un errore durante l'aggiornamento dei dati in background. Ricarica la pagina per vedere gli ultimi dati.", "empty": "Il parametro non può essere vuoto.", "saveEntityFailed": "Impossibile salvare l'entità perché %s", "getEntityFailed": "Impossibile recuperare l'entità perché %s", @@ -82,7 +84,9 @@ "merchantError": "Commerciante con ID %s non trovato.", "failedToRegisterWebhook": "Impossibile registrare il webhook.", "hmacError": "Errore nella generazione dell'HMAC." - } + }, + "failedToRetrieveLog": "Impossibile recuperare il contenuto del registro.", + "failedToRemoveLog": "Impossibile rimuovere il contenuto del registro." } }, "sidebar": { @@ -114,7 +118,8 @@ "invalidFieldValue": "Il campo deve essere impostato su \"%s\".", "invalidMaxValue": "Il valore massimo consentito è %s.", "invalidImageType": "Fornire un file in uno dei tipi di immagine supportati (PNG, JPG, SVG).", - "invalidImageSize": "Il file selezionato supera il limite di 10MB." + "invalidImageSize": "Il file selezionato supera il limite di 10MB.", + "invalidTimeDuration": "Questo campo deve contenere solo date come 2017-08-31 o durate come P3M15D (3 mesi e 15 giorni). Controlla ISO 8601." }, "payments": { "title": "Metodi di pagamento", @@ -129,6 +134,24 @@ "availableFrom": "Disponibile da", "availableTo": "Disponibile fino a" }, + "debug": { + "title": "Abilita registri", + "description": "Aiuta a diagnosticare problemi con l'integrazione SeQura", + "noLogs": "Non ci sono registri disponibili", + "log": { + "severity": "Gravità", + "message": "Messaggio", + "datetime": "Data e ora", + "removeModal": { + "title": "Elimina registri", + "message": "Sei sicuro di voler cancellare i registri? Non potrai recuperarli." + }, + "minLevel": "Livello di gravità minimo", + "minLevelDescription": "Imposta il livello di gravità minimo dei registri da scrivere nel file di log." + }, + "reload": "Ricarica", + "remove": "Rimuovi" + }, "connection": { "title": { "onboarding": "Iniziare", @@ -210,7 +233,8 @@ "PE": { "label": "Perù", "description": "Inserisci l'ID commerciante SeQura da utilizzare per i clienti peruviani." - } + }, + "enabledCountries": "Attivato per {countries}." }, "widgets": { "title": { @@ -295,7 +319,7 @@ }, "orderStatusSettings": { "title": "Impostazioni stato ordine", - "description": "Configura il tuo account SeQura e scegli l'ambiente per iniziare a elaborare i pagamenti.", + "description": "Definisci come gli stati di seQura si mappano sugli stati degli ordini di {shopName}. I valori predefiniti dovrebbero funzionare nella maggior parte dei casi, quindi modificali con cautela.", "paid": { "label": "Pagato", "description": "Quando un pagamento ha stato Pagato." @@ -335,6 +359,26 @@ "excludedProducts": { "label": "Prodotti esclusi", "description": "I prodotti del negozio in questo campo non potranno essere acquistati con i metodi di pagamento SeQura. Inserisci un SKU valido per ogni prodotto che vuoi escludere." + }, + "enabledForServices": { + "label": "Abilitato per i servizi", + "description": "Il tuo contratto deve consentire la vendita di servizi. " + }, + "allowFirstServicePaymentDelay": { + "label": "Consenti ritardo nel primo pagamento del servizio", + "description": "" + }, + "allowServiceRegistrationItems": { + "label": "Consenti articoli di registrazione", + "description": "Consente di configurare una parte del prezzo del prodotto da pagare in anticipo come registrazione. " + }, + "defaultServicesEndDate": { + "label": "Data di fine dei servizi predefinita", + "description": "Date come 2017-08-31, durata come P3M15D (3 mesi e 15 giorni). Valore predefinito per tutti i prodotti." } + }, + "supportLink": { + "label": "Hai bisogno di aiuto?", + "link": "https://sqra.es/supportit" } -} +} \ No newline at end of file diff --git a/view/adminhtml/web/lang/pt.json b/view/adminhtml/web/lang/pt.json index 6a0e819c..addc3a91 100644 --- a/view/adminhtml/web/lang/pt.json +++ b/view/adminhtml/web/lang/pt.json @@ -14,6 +14,7 @@ "merchant": "Comerciante:", "settings": "Definições", "paymentMethods": "Métodos de pagamento", + "advanced": "Avançado", "changesSaved": "Alterações guardadas com sucesso.", "noItemsAvailable": "Nenhum item disponível", "and": " e ", @@ -24,6 +25,7 @@ "errors": { "InvalidOperationException": "Ocorreu um erro no lado do servidor. Por favor, verifique os dados e tente novamente.", "unknown": "Ocorreu um erro desconhecido. Por favor, tente novamente mais tarde.", + "backgroundDataFetchFailure": "Ocorreu um erro ao atualizar os dados em segundo plano. Por favor, recarregue a página para ver os dados mais recentes.", "empty": "O parâmetro não pode estar vazio.", "saveEntityFailed": "Falha ao guardar entidade porque %s", "getEntityFailed": "Falha ao obter entidade porque %s", @@ -82,7 +84,9 @@ "merchantError": "Comerciante com ID %s não encontrado.", "failedToRegisterWebhook": "Falha ao registar webhook.", "hmacError": "Erro ao gerar HMAC." - } + }, + "failedToRetrieveLog": "Falha ao recuperar o conteúdo do log.", + "failedToRemoveLog": "Falha ao remover o conteúdo do log." } }, "sidebar": { @@ -115,7 +119,8 @@ "invalidFieldValue": "O campo deve ser definido como \"%s\".", "acceptableRange": "O valor deve estar entre %d e %d.", "paymentMethodUnavailable": "Método de pagamento indisponível.", - "storeUnavailable": "Loja indisponível." + "storeUnavailable": "Loja indisponível.", + "invalidTimeDuration": "Este campo deve conter apenas datas como 2017-08-31 ou durações como P3M15D (3 meses e 15 dias). Verifique o formato ISO 8601" }, "payments": { "title": "Métodos de pagamento", @@ -130,6 +135,24 @@ "availableFrom": "Disponível a partir de", "availableTo": "Disponível até" }, + "debug": { + "title": "Habilitar registros", + "description": "Ajuda a diagnosticar problemas com a integração SeQura", + "noLogs": "Não há registros disponíveis", + "log": { + "severity": "Severidade", + "message": "Mensagem", + "datetime": "Data e hora", + "removeModal": { + "title": "Remover registros", + "message": "Tem certeza de que deseja limpar os registros? Você não poderá recuperá-los." + }, + "minLevel": "Nível mínimo de severidade", + "minLevelDescription": "Defina o nível mínimo de severidade dos registros a serem gravados no arquivo de log." + }, + "reload": "Recarregar", + "remove": "Remover" + }, "connection": { "title": { "onboarding": "Primeiros passos", @@ -211,7 +234,8 @@ "PE": { "label": "Peru", "description": "Por favor, insira o ID de comerciante SeQura que deve ser usado para clientes peruanos." - } + }, + "enabledCountries": "Ativado para {countries}." }, "widgets": { "title": { @@ -296,7 +320,7 @@ }, "orderStatusSettings": { "title": "Definições de estado do pedido", - "description": "Estado do pedido Configure a sua conta seQura e escolha o ambiente para começar a processar pagamentos. ", + "description": "Defina como os status do seQura se relacionam com os status de pedido do {shopName}. Os valores padrão devem funcionar na maioria dos casos, portanto, altere-os com cautela.", "paid": { "label": "Pago", "description": "Quando um pagamento tem estado Pago." @@ -336,6 +360,26 @@ "excludedProducts": { "label": "Produtos excluídos", "description": "Os produtos da loja neste campo não serão autorizados a ser comprados com métodos de pagamento SeQura. Por favor, insira um SKU válido para cada produto que gostaria de excluir." + }, + "enabledForServices": { + "label": "Habilitado para serviços", + "description": "Seu contrato deve permitir a venda de serviços. " + }, + "allowFirstServicePaymentDelay": { + "label": "Permitir atraso no primeiro pagamento do serviço", + "description": "" + }, + "allowServiceRegistrationItems": { + "label": "Permitir itens de registro", + "description": "Permite configurar uma parte do preço do produto a ser paga antecipadamente como registro. " + }, + "defaultServicesEndDate": { + "label": "Data de término dos serviços padrão", + "description": "Datas como 2017-08-31, duração como P3M15D (3 meses e 15 dias). Valor padrão para todos os produtos." } + }, + "supportLink": { + "label": "Precisa de ajuda?", + "link": "https://sqra.es/supportpt" } -} +} \ No newline at end of file From 146c0bb48e6bdb23136bf1ee044aa4b0fd3cb21c Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Wed, 10 Dec 2025 10:00:41 +0100 Subject: [PATCH 02/16] Update CORE version ISSUE: LIS-90 --- .../Configuration/WidgetSettings.php | 15 -- .../PromotionalWidget/WidgetConfigurator.php | 28 ++++ composer.json | 154 +++++++++--------- 3 files changed, 105 insertions(+), 92 deletions(-) diff --git a/Controller/Adminhtml/Configuration/WidgetSettings.php b/Controller/Adminhtml/Configuration/WidgetSettings.php index c1b28380..983abe96 100644 --- a/Controller/Adminhtml/Configuration/WidgetSettings.php +++ b/Controller/Adminhtml/Configuration/WidgetSettings.php @@ -55,23 +55,8 @@ protected function getWidgetSettings(): Json { /** @var WidgetSettingsResponse $data */ $data = AdminAPI::get()->widgetConfiguration($this->storeId)->getWidgetSettings(); - $result = $data->toArray(); - if (empty($result)) { - $result['productPriceSelector'] = '.price-container .price'; - $result['defaultProductLocationSelector'] = '.actions .action.primary.tocart'; - $result['altProductPriceSelector'] = '[data-price-type="finalPrice"] .price'; - $result['altProductPriceTriggerSelector'] = '.bundle-actions'; - $result['cartPriceSelector'] = '.grand.totals .price'; - $result['cartLocationSelector'] = '.cart-summary'; - - return $this->result->setData($result); - } - - $result['widgetStyles'] = $result['widgetConfiguration']; - unset($result['widgetConfiguration']); - $this->addResponseCode($data); return $this->result->setData($result); diff --git a/Services/BusinessLogic/PromotionalWidget/WidgetConfigurator.php b/Services/BusinessLogic/PromotionalWidget/WidgetConfigurator.php index 0bbf26e1..8d89e13e 100644 --- a/Services/BusinessLogic/PromotionalWidget/WidgetConfigurator.php +++ b/Services/BusinessLogic/PromotionalWidget/WidgetConfigurator.php @@ -7,6 +7,7 @@ use Magento\Store\Model\Store; use NumberFormatter; use SeQura\Core\BusinessLogic\Domain\Integration\PromotionalWidgets\WidgetConfiguratorInterface; +use SeQura\Core\BusinessLogic\Domain\PromotionalWidgets\Models\WidgetSettings; class WidgetConfigurator implements WidgetConfiguratorInterface { @@ -83,6 +84,33 @@ public function getThousandsSeparator(): string return (string) $this->getFormatter()->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); } + /** + * Returns an instance of WidgetSettings having the default values. + * See SeQura\Core\BusinessLogic\Domain\PromotionalWidgets\Models\WidgetSettings::createDefault(). + */ + public function getDefaultWidgetSettings(): WidgetSettings + { + $productPriceSelector = '.price-container .price'; + $productLocationSelector = '.actions .action.primary.tocart'; + $cartPriceSelector = '.grand.totals .price'; + $cartLocationSelector = '.cart-summary'; + $listingPriceSelector = ''; + $listingLocationSelector = ''; + $altProductPriceSelector = '[data-price-type="finalPrice"] .price'; + $altProductPriceTriggerSelector = '.bundle-actions'; + + return WidgetSettings::createDefault( + $productPriceSelector, + $productLocationSelector, + $cartPriceSelector, + $cartLocationSelector, + $listingPriceSelector, + $listingLocationSelector, + $altProductPriceSelector, + $altProductPriceTriggerSelector + ); + } + /** * Get formatter instance. * diff --git a/composer.json b/composer.json index cc4ebc6b..df85b761 100644 --- a/composer.json +++ b/composer.json @@ -1,77 +1,77 @@ -{ - "name": "sequra/magento2-core", - "description": "Core module for SeQura Payment Methods", - "type": "magento2-module", - "keywords": [ - "payment", - "sequra", - "pagos", - "magento2" - ], - "version": "3.1.0", - "license": "MIT", - "authors": [ - { - "name": "Sequra Engineering", - "email": "dev@sequra.es" - } - ], - "minimum-stability": "dev", - "prefer-stable": true, - "require": { - "php": ">=7.4", - "sequra/integration-core": "^3.0.0", - "ext-json": "*" - }, - "autoload": { - "files": [ - "registration.php" - ], - "psr-4": { - "Sequra\\Core\\": "" - } - }, - "autoload-dev": { - "psr-4": { - "SeQura\\Core\\Tests\\Infrastructure\\": "vendor/sequra/integration-core/tests/Infrastructure", - "SeQura\\Core\\Tests\\BusinessLogic\\": "vendor/sequra/integration-core/tests/BusinessLogic" - } - }, - "archive": { - "exclude": [ - ".docker", - ".github", - "bin", - "docker-entrypoint-init.d", - "Test", - ".env", - ".env.sample", - ".gitattributes", - ".gitignore", - "docker-compose.override.sample.yml", - "docker-compose.yml", - "README.md", - "setup.sh", - "teardown.sh", - "tests-e2e", - "package.json", - "package-lock.json", - "playwright.config.js" - ] - }, - "require-dev": { - "magento/magento-coding-standard": "^38.0" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "repositories": [ - { - "type": "git", - "url": "git@github.com:sequra/integration-core.git" - } - ] -} - +{ + "name": "sequra/magento2-core", + "description": "Core module for SeQura Payment Methods", + "type": "magento2-module", + "keywords": [ + "payment", + "sequra", + "pagos", + "magento2" + ], + "version": "3.1.0", + "license": "MIT", + "authors": [ + { + "name": "Sequra Engineering", + "email": "dev@sequra.es" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=7.4", + "sequra/integration-core": "dev-feature/LIS-90", + "ext-json": "*" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Sequra\\Core\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "SeQura\\Core\\Tests\\Infrastructure\\": "vendor/sequra/integration-core/tests/Infrastructure", + "SeQura\\Core\\Tests\\BusinessLogic\\": "vendor/sequra/integration-core/tests/BusinessLogic" + } + }, + "archive": { + "exclude": [ + ".docker", + ".github", + "bin", + "docker-entrypoint-init.d", + "Test", + ".env", + ".env.sample", + ".gitattributes", + ".gitignore", + "docker-compose.override.sample.yml", + "docker-compose.yml", + "README.md", + "setup.sh", + "teardown.sh", + "tests-e2e", + "package.json", + "package-lock.json", + "playwright.config.js" + ] + }, + "require-dev": { + "magento/magento-coding-standard": "^38.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "repositories": [ + { + "type": "git", + "url": "git@github.com:sequra/integration-core.git" + } + ] +} + From aa61d9dedc0fbe3946fb10c552a947f347507171 Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Mon, 29 Dec 2025 08:39:39 +0100 Subject: [PATCH 03/16] Update CORE UI version to 2.0.2 ISSUE: LIS-90 --- package-lock.json | 12 +++---- package.json | 2 +- view/adminhtml/web/js/OnboardingController.js | 10 +----- view/adminhtml/web/js/StateController.js | 2 +- view/adminhtml/web/js/ValidationService.js | 28 ++++++++------- view/adminhtml/web/js/WidgetSettingsForm.js | 35 +++++++------------ 6 files changed, 38 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6cb4ace..a58d2002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@playwright/test": "^1.56.1", "@types/node": "^22.10.7", "playwright-fixture-for-plugins": "github:sequra/playwright-fixture-for-plugins", - "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.1" + "sequra-core-admin-fe": "github:sequra/integration-core-ui#feature/LIS-90" } }, "node_modules/@playwright/test": { @@ -32,9 +32,9 @@ } }, "node_modules/@types/node": { - "version": "22.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", "dependencies": { @@ -116,8 +116,8 @@ "license": "MIT" }, "node_modules/sequra-core-admin-fe": { - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/sequra/integration-core-ui.git#ed85c2c460cd1ff73fc5aebfc06a518248b4a8f1", + "version": "2.0.2", + "resolved": "git+ssh://git@github.com/sequra/integration-core-ui.git#8d0de30d6576fa38b5aeee887133e8fe8a825c1b", "dev": true, "dependencies": { "json-formatter-js": "^2.5.23", diff --git a/package.json b/package.json index 7a6d0546..a09b0944 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "devDependencies": { - "sequra-core-admin-fe": "github:sequra/integration-core-ui#2.0.1", + "sequra-core-admin-fe": "github:sequra/integration-core-ui#feature/LIS-90", "@playwright/test": "^1.56.1", "@types/node": "^22.10.7", "playwright-fixture-for-plugins": "github:sequra/playwright-fixture-for-plugins" diff --git a/view/adminhtml/web/js/OnboardingController.js b/view/adminhtml/web/js/OnboardingController.js index d20cc614..dfe1ee1a 100644 --- a/view/adminhtml/web/js/OnboardingController.js +++ b/view/adminhtml/web/js/OnboardingController.js @@ -85,10 +85,6 @@ if (!window.SequraFE) { case SequraFE.appPages.ONBOARDING.WIDGETS: renderer = renderWidgetSettingsForm; promises = Promise.all([ - SequraFE.state.getData('paymentMethods') ?? api.get(configuration.getPaymentMethodsUrl.replace( - '{merchantId}', - countrySettings[0].merchantId - ), null, SequraFE.customHeader), SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl, null, SequraFE.customHeader), ]) break; @@ -153,17 +149,13 @@ if (!window.SequraFE) { * @param allAvailablePaymentMethods */ const renderWidgetSettingsForm = (paymentMethods, allAvailablePaymentMethods) => { - if (!SequraFE.state.getData('paymentMethods')) { - SequraFE.state.setData('paymentMethods', paymentMethods) - } - if (!SequraFE.state.getData('allAvailablePaymentMethods')) { SequraFE.state.setData('allAvailablePaymentMethods', allAvailablePaymentMethods) } const form = formFactory.getInstance( 'widgetSettings', - {widgetSettings, connectionSettings, countrySettings, paymentMethods, allAvailablePaymentMethods}, + {widgetSettings, connectionSettings, countrySettings, allAvailablePaymentMethods}, {...configuration, appState: SequraFE.appStates.ONBOARDING} ); diff --git a/view/adminhtml/web/js/StateController.js b/view/adminhtml/web/js/StateController.js index 6350f13d..fe0723df 100644 --- a/view/adminhtml/web/js/StateController.js +++ b/view/adminhtml/web/js/StateController.js @@ -8,7 +8,7 @@ SequraFE.flags = { configurableSelectorsForMiniWidgets: false, isServiceSellingAllowed: false, ...(SequraFE.flags || {}) -} +}; SequraFE.appStates = { ONBOARDING: 'onboarding', diff --git a/view/adminhtml/web/js/ValidationService.js b/view/adminhtml/web/js/ValidationService.js index 5d6fa3e9..2dc7286c 100644 --- a/view/adminhtml/web/js/ValidationService.js +++ b/view/adminhtml/web/js/ValidationService.js @@ -14,7 +14,7 @@ if (typeof SequraFE.regex === 'undefined'){ * @property {string} message The error message. */ - /** + /** * @typedef CategoryPaymentMethod * @property {string|null} category * @property {string|null} product @@ -283,8 +283,9 @@ if (typeof SequraFE.regex === 'undefined'){ /** * Validates custom locations. - * @param {Array} element Each element in the array should be the details element containing the custom location data. - * @param {Array} value + * @param {Array} element Each element in the array should be the details element containing the + * custom location data. + * @param {Array} value * @param {string} value[].selForTarget CSS selector for the target element. * @param {string} value[].widgetStyles JSON string representing the styles for the widget. * @param {string} value[].product Product name. @@ -324,15 +325,18 @@ if (typeof SequraFE.regex === 'undefined'){ } /** - * Validates related fields and disables the footer if any of them is invalid. - * @param {string} parentField The parent field name that controls the visibility of related fields. - * @param {Array} fieldsRelationships An array of objects containing the relationships between fields. - * @param {string} fieldsRelationships[].parentField The parent field name that controls the visibility of related fields. - * @param {Array} fieldsRelationships[].requiredFields An array of field names that are required when the parent field is shown. - * @param {Array} fieldsRelationships[].fields An array of field names that are related to the parent field. - * @param {boolean} show Whether to show or hide the related fields. - * @return {boolean} Returns true if all related fields are valid, false otherwise. - */ + * Validates related fields and disables the footer if any of them is invalid. + * @param {string} parentField The parent field name that controls the visibility of related fields. + * @param {Array} fieldsRelationships An array of objects containing the relationships between fields. + * @param {string} fieldsRelationships[].parentField The parent field name that controls the visibility of related + * fields. + * @param {Array} fieldsRelationships[].requiredFields An array of field names that are required when the + * parent field is shown. + * @param {Array} fieldsRelationships[].fields An array of field names that are related to the parent + * field. + * @param {boolean} show Whether to show or hide the related fields. + * @return {boolean} Returns true if all related fields are valid, false otherwise. + */ const validateRelatedFields = (parentField, fieldsRelationships, show) => { if (!show) { return true; diff --git a/view/adminhtml/web/js/WidgetSettingsForm.js b/view/adminhtml/web/js/WidgetSettingsForm.js index 60834315..0f8e8ad9 100644 --- a/view/adminhtml/web/js/WidgetSettingsForm.js +++ b/view/adminhtml/web/js/WidgetSettingsForm.js @@ -12,7 +12,7 @@ if (!window.SequraFE) { /** * @typedef Category - * @property {CategoryPaymentMethod[]} paymentMethods + * @property {CategoryPaymentMethod[]} categoryPaymentMethods */ /** @@ -52,15 +52,14 @@ if (!window.SequraFE) { * widgetSettings: WidgetSettings, * connectionSettings: ConnectionSettings, * countrySettings: CountrySettings[], - * paymentMethods: PaymentMethod[], * allAvailablePaymentMethods: Category[], * }} data * @param {{ * saveWidgetSettingsUrl: string, - * getPaymentMethodsUrl: string, * getAllPaymentMethodsUrl: string, * page: string, - * appState: string + * appState: string, + * configurableSelectorsForMiniWidgets: string, * }} configuration * @constructor */ @@ -80,13 +79,9 @@ if (!window.SequraFE) { let activeSettings; /** @type WidgetSettings */ let changedSettings; - /** @type string[] */ - let paymentMethodIds; /** @type {Category[]} */ let allAvailablePaymentMethods = data.allAvailablePaymentMethods; /** @type {CategoryPaymentMethod[]} */ - let payNowPaymentMethods = allAvailablePaymentMethods.pay_now ?? []; - /** @type {CategoryPaymentMethod[]} */ let payLaterPaymentMethods = allAvailablePaymentMethods.pay_later ?? []; /** @type {CategoryPaymentMethod[]} */ let partPaymentPaymentMethods = allAvailablePaymentMethods.part_payment ?? []; @@ -123,7 +118,6 @@ if (!window.SequraFE) { } } - paymentMethodIds = data.paymentMethods?.map((paymentMethod) => paymentMethod.product); changedSettings = utilities.cloneObject(activeSettings) initForm(); @@ -423,7 +417,7 @@ if (!window.SequraFE) { return ``; }).join('') : '' - } + } ` @@ -503,15 +497,6 @@ if (!window.SequraFE) { disableFooter(!validate()); } - /** - * Re-renders the form. - */ - const refreshForm = () => { - document.querySelector('.sq-content-inner')?.remove(); - configuration.appState !== SequraFE.appStates.ONBOARDING && document.querySelector('.sq-page-footer').remove(); - initForm(); - } - /** * Handles the saving of the form. */ @@ -524,10 +509,16 @@ if (!window.SequraFE) { api.post(configuration.saveWidgetSettingsUrl, changedSettings, SequraFE.customHeader) .then(() => { if (configuration.appState === SequraFE.appStates.ONBOARDING) { - const index = SequraFE.pages.onboarding.indexOf(SequraFE.appPages.ONBOARDING.WIDGETS) - SequraFE.pages.onboarding.length > index + 1 ? - window.location.hash = configuration.appState + '-' + SequraFE.pages.onboarding[index + 1] : + const index = SequraFE.pages.onboarding.indexOf(SequraFE.appPages.ONBOARDING.WIDGETS); + + const nextPageExists = SequraFE.pages.onboarding.length > index + 1; + if (nextPageExists) { + window.location.hash = configuration.appState + '-' + SequraFE.pages.onboarding[index + 1]; + } else if (!SequraFE.isPromotional) { window.location.hash = SequraFE.appStates.PAYMENT + '-' + SequraFE.appPages.PAYMENT.METHODS; + } else { + window.location.hash = SequraFE.appStates.SETTINGS + '-' + SequraFE.appPages.SETTINGS.WIDGET; + } } activeSettings = utilities.cloneObject(changedSettings); From 3a70944d94f47a2948920c5fa3a769b7ee27a4bb Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Mon, 29 Dec 2025 08:49:43 +0100 Subject: [PATCH 04/16] Use encodeURIComponent to replace values in URL path ISSUE: LIS-90 --- .../web/js/override/PaymentController.js | 248 +++++++++ .../web/js/override/SettingsController.js | 309 ++++++++++++ .../web/js/override/StateController.js | 474 ++++++++++++++++++ 3 files changed, 1031 insertions(+) create mode 100644 view/adminhtml/web/js/override/PaymentController.js create mode 100644 view/adminhtml/web/js/override/SettingsController.js create mode 100644 view/adminhtml/web/js/override/StateController.js diff --git a/view/adminhtml/web/js/override/PaymentController.js b/view/adminhtml/web/js/override/PaymentController.js new file mode 100644 index 00000000..e6093bca --- /dev/null +++ b/view/adminhtml/web/js/override/PaymentController.js @@ -0,0 +1,248 @@ +if (!window.SequraFE) { + window.SequraFE = {}; +} + +(function () { + /** + * @typedef PaymentMethod + * @property {string} product + * @property {string} title + * @property {string | null} description + * @property {number | null} minAmount + * @property {number | null} maxAmount + * @property {string | null} startsAt + * @property {string | null} endsAt + */ + + /** + * Handles payment methods page logic. + * + * @param {{ + * getPaymentMethodsUrl: string, + * getSellingCountriesUrl: string, + * getCountrySettingsUrl: string, + * getConnectionDataUrl: string + * validateConnectionDataUrl: string + * }} configuration + * @constructor + */ + function PaymentController(configuration) { + const {templateService, elementGenerator: generator, components, utilities} = SequraFE; + /** @type AjaxServiceType */ + const api = SequraFE.ajaxService; + /** @type string */ + let currentStoreId = ''; + /** @type Version */ + let version; + /** @type Store[] */ + let stores; + /** @type SellingCountry[] */ + let sellingCountries; + /** @type PaymentMethod[] */ + let paymentMethods; + /** @type CountrySettings[] */ + let countryConfiguration; + /** @type ConnectionSettings */ + let connectionSettings; + + /** + * Displays page content. + * + * @param {{ state?: string, storeId: string }} config + */ + this.display = ({storeId}) => { + currentStoreId = storeId; + templateService.clearMainPage(); + + stores = SequraFE.state.getData('stores'); + version = SequraFE.state.getData('version'); + connectionSettings = SequraFE.state.getData('connectionSettings'); + countryConfiguration = SequraFE.state.getData('countrySettings'); + sellingCountries = SequraFE.state.getData('sellingCountries'); + paymentMethods = SequraFE.state.getData('paymentMethods'); + + if (paymentMethods && sellingCountries) { + initializePage(); + utilities.hideLoader(); + + return; + } + + Promise.all([ + sellingCountries ? [] : api.get(configuration.getSellingCountriesUrl, null, SequraFE.customHeader), + paymentMethods ? [] : api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), countryConfiguration[0].merchantId)), + ]).then(([sellingCountriesRes, paymentMethodsRes]) => { + if (sellingCountriesRes.length !== 0) { + sellingCountries = sellingCountriesRes; + SequraFE.state.setData('sellingCountries', sellingCountriesRes) + } + + if (paymentMethodsRes.length !== 0) { + paymentMethods = paymentMethodsRes; + SequraFE.state.setData('paymentMethods', paymentMethodsRes) + } + + initializePage(); + }).finally(() => utilities.hideLoader()); + }; + + /** + * Renders the page contents. + */ + const initializePage = () => { + const pageWrapper = document.getElementById('sq-page-wrapper'); + + pageWrapper.append( + generator.createElement('div', 'sq-page-content-wrapper sqv--payments', '', null, [ + SequraFE.components.PageHeader.create( + { + currentVersion: version.current, + newVersion: { + versionLabel: version.new, + versionUrl: version.downloadNewVersionUrl + }, + mode: connectionSettings.environment === 'live' ? connectionSettings.environment : 'test', + activeStore: currentStoreId, + stores: stores.map((store) => ({label: store.storeName, value: store.storeId})), + onChange: (storeId) => { + if (storeId !== SequraFE.state.getStoreId()) { + SequraFE.state.setStoreId(storeId); + window.location.hash = ''; + SequraFE.state.display(); + } + }, + menuItems: SequraFE.utilities.getMenuItems(SequraFE.appStates.PAYMENT) + } + ), + generator.createElement('div', 'sq-page-content', '', null, [ + generator.createElement('div', 'sq-content-row', '', null, [ + generator.createElement('main', 'sq-content', '', null, [ + generator.createElement('div', 'sq-content-inner', '', null, [ + generator.createElement('div', 'sq-table-heading', '', null, [ + generator.createPageHeading({ + title: 'payments.title', + text: 'payments.description' + }), + sellingCountries.length > 0 ? generator.createDropdownField({ + className: 'sqm--table-dropdown', + label: 'payments.countries.label', + description: 'payments.countries.description', + value: countryConfiguration[0].merchantId, + options: countryConfiguration.map((countrySetting) => { + return { + label: sellingCountries.find((country) => + countrySetting.countryCode === country.code + ).name, value: countrySetting.merchantId + } + }), + onChange: handleCountryChange + }) : [], + ]), + sellingCountries.length > 0 ? components.DataTable.create(getTableHeaders(), getTableRows()) : [] + ]), + ]) + ]) + ]), + generator.createSupportLink() + ])) + } + + /** + * Returns table headers. + * + * @returns {TableCell[]} + */ + const getTableHeaders = () => { + return [ + {label: 'payments.paymentMethods', className: 'sqp-payment-method-header-cell sqm--text-left'}, + {label: 'payments.minimumAmount'}, + {label: 'payments.maximumAmount'}, + {label: 'payments.availableFrom'}, + {label: 'payments.availableTo'}, + ]; + } + + /** + * Returns table rows. + * + * @returns {TableCell[][]} + */ + const getTableRows = () => { + if (!paymentMethods) { + return []; + } + return paymentMethods.map((method) => { + return [ + { + className: 'sqp-payment-method-cell sqm--text-left', + renderer: (cell) => { + const icon = method.icon ? method.icon : (SequraFE.imagesProvider.icons.payment || ''); + cell.prepend( + generator.createElementFromHTML(icon), + generator.createElement('div', '', '', null, [ + generator.createElement('h3', 'sqp-payment-method-title', method.title), + generator.createElement( + 'p', + 'sqp-payment-method-description', + method?.description ?? '' + ) + ]) + ) + } + }, + {label: method?.minAmount ? formatAmount(method.minAmount) : '/'}, + {label: method?.maxAmount ? formatAmount(method.maxAmount) : '/'}, + {label: method?.startsAt ? formatDate(method.startsAt) : '/'}, + {label: method?.endsAt ? formatDate(method.endsAt) : '/'} + ]; + }); + } + + /** + * Handles the customer country change. + * + * @param value + */ + const handleCountryChange = (value) => { + utilities.showLoader(); + api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), value)) + .then((methods) => { + paymentMethods = [...methods]; + document.querySelector('.sq-table-container').remove(); + document.querySelector('.sq-content-inner')?.append( + components.DataTable.create(getTableHeaders(), getTableRows()) + ) + }) + .finally(utilities.hideLoader); + } + + /** + * Formats the given date to the required table view. + * + * @param date + * + * @returns {`${string}/${string}/${number}`} + */ + const formatDate = (date) => { + const dateTime = new Date(date.replace(/ /g, "T")); + const day = String(dateTime.getDate()).padStart(2, '0'); + const month = String(dateTime.getMonth() + 1).padStart(2, '0'); + const year = dateTime.getFullYear(); + + return `${day}/${month}/${year}`; + } + + /** + * Formats the given amount to the required table view. + * + * @param amount + * + * @returns {string} + */ + const formatAmount = (amount) => { + return (amount / 100).toLocaleString('es', {minimumFractionDigits: 2}) + ' EUR'; + } + } + + SequraFE.PaymentController = PaymentController; +})(); diff --git a/view/adminhtml/web/js/override/SettingsController.js b/view/adminhtml/web/js/override/SettingsController.js new file mode 100644 index 00000000..388d81d6 --- /dev/null +++ b/view/adminhtml/web/js/override/SettingsController.js @@ -0,0 +1,309 @@ +if (!window.SequraFE) { + window.SequraFE = {}; +} + +(function () { + /** + * Handles settings page logic. + * + * @param {{ + * getConnectionDataUrl: string, + * getWidgetSettingsUrl: string, + * getGeneralSettingsUrl: string, + * getOrderStatusMappingSettingsUrl: string, + * getShopOrderStatusesUrl: string, + * getShopCategoriesUrl: string, + * getSellingCountriesUrl: string, + * getCountrySettingsUrl: string, + * getPaymentMethodsUrl: string, + * getAllAvailablePaymentMethodsUrl: string, + * validateConnectionDataUrl: string, + * disconnectUrl: string, + * page: string + * }} configuration + * @constructor + */ + function SettingsController(configuration) { + const {templateService, elementGenerator: generator, utilities, formFactory} = SequraFE; + + /** @type AjaxServiceType */ + const api = SequraFE.ajaxService; + let currentStoreId = ''; + /** @type Version */ + let version; + /** @type Store[] */ + let stores; + /** @type CountrySettings[] **/ + let countrySettings; + /** @type ConnectionSettings **/ + let connectionSettings; + /** @type WidgetSettings **/ + let widgetSettings; + + /** + * Displays page content. + * + * @param {{ state?: string, storeId: string }} config + */ + this.display = ({storeId}) => { + utilities.showLoader(); + currentStoreId = storeId; + templateService.clearMainPage(); + stores = SequraFE.state.getData('stores'); + version = SequraFE.state.getData('version'); + connectionSettings = SequraFE.state.getData('connectionSettings'); + countrySettings = SequraFE.state.getData('countrySettings'); + widgetSettings = SequraFE.state.getData('widgetSettings'); + + initializePage(); + renderPage(); + }; + + /** + * Handles rendering of a form based on state. + */ + const renderPage = () => { + utilities.showLoader(); + let page = SequraFE.state.getPage(); + let renderer; + let promises; + + if (!SequraFE.pages.settings.includes(page)) { + page = SequraFE.pages.settings[0]; + } + + switch (page) { + case SequraFE.appPages.SETTINGS.CONNECTION: + renderer = renderConnectionSettingsForm; + promises = Promise.all([ + SequraFE.state.getData('notConnectedDeployments') ?? api.get( + configuration.pageConfiguration.onboarding.getNotConnectedDeploymentsUrl.replace( + encodeURIComponent('{storeId}'), SequraFE.state.getStoreId() + ), + null, + SequraFE.customHeader + ), + ]) + break; + case SequraFE.appPages.SETTINGS.ORDER_STATUS: + renderer = renderOrderStatusMappingSettingsForm; + promises = Promise.all([ + api.get(configuration.getOrderStatusMappingSettingsUrl, null, SequraFE.customHeader), + api.get(configuration.getShopOrderStatusesUrl, null, SequraFE.customHeader), + SequraFE.state.getShopName() + ]) + break; + case SequraFE.appPages.SETTINGS.WIDGET: + renderer = renderWidgetSettingsForm; + promises = Promise.all([ + SequraFE.state.getData('paymentMethods') ?? api.get(configuration.getPaymentMethodsUrl.replace(encodeURIComponent('{merchantId}'), countrySettings[0].merchantId)), + SequraFE.state.getData('allAvailablePaymentMethods') ?? api.get(configuration.getAllAvailablePaymentMethodsUrl, null, SequraFE.customHeader), + ]) + break; + default: + renderer = renderGeneralSettingsForm; + promises = Promise.all([ + SequraFE.isPromotional ? [] : + SequraFE.state.getData('generalSettings') ?? api.get(configuration.getGeneralSettingsUrl, null, SequraFE.customHeader), + SequraFE.isPromotional ? [] : + SequraFE.state.getData('shopCategories') ?? api.get(configuration.getShopCategoriesUrl, null, SequraFE.customHeader), + SequraFE.state.getData('sellingCountries') ?? api.get(configuration.getSellingCountriesUrl, null, SequraFE.customHeader), + ]) + } + + promises + .then((array) => renderer(...array)) + }; + + /** + * Renders the connection settings form. + */ + const renderConnectionSettingsForm = (notConnectedDeployments) => { + const activeDeploymentsIds = connectionSettings?.connectionData?.map( + cd => cd.deployment + ).filter(Boolean) || []; + + notConnectedDeployments = Array.isArray(notConnectedDeployments) + ? notConnectedDeployments.filter(deployment => !activeDeploymentsIds.includes(deployment.id)) + : []; + + SequraFE.state.setData('notConnectedDeployments', notConnectedDeployments); + + const form = formFactory.getInstance( + 'connectionSettings', + {connectionSettings, countrySettings, activeDeploymentsIds, notConnectedDeployments}, + {...configuration, appState: SequraFE.appStates.SETTINGS} + ); + + form?.render(); + } + + /** + * Renders the order status mappings settings form. + * + * @param orderStatusSettings + * @param shopOrderStatuses + * @param shopName + */ + const renderOrderStatusMappingSettingsForm = (orderStatusSettings, shopOrderStatuses, shopName) => { + const form = formFactory.getInstance( + 'orderStatusMappingSettings', + {orderStatusSettings, shopOrderStatuses, shopName: shopName.shopName}, + {...configuration} + ); + + form?.render(); + } + + /** + * Renders the widget settings form. + * + * @param paymentMethods + * @param allAvailablePaymentMethods + */ + const renderWidgetSettingsForm = (paymentMethods, allAvailablePaymentMethods) => { + if (!SequraFE.state.getData('paymentMethods')) { + SequraFE.state.setData('paymentMethods', paymentMethods) + } + + if (!SequraFE.state.getData('allAvailablePaymentMethods')) { + SequraFE.state.setData('allAvailablePaymentMethods', allAvailablePaymentMethods) + } + + const form = formFactory.getInstance( + 'widgetSettings', + {widgetSettings, connectionSettings, countrySettings, paymentMethods, allAvailablePaymentMethods}, + {...configuration, appState: SequraFE.appStates.SETTINGS} + ); + + form?.render(); + } + + /** + * Renders the general settings form. + * + * @param generalSettings + * @param shopCategories + * @param sellingCountries + */ + const renderGeneralSettingsForm = ( + generalSettings, + shopCategories, + sellingCountries, + ) => { + saveFetchedDataToDataStore(generalSettings, shopCategories, sellingCountries); + + const form = formFactory.getInstance( + 'generalSettings', + {generalSettings, shopCategories, sellingCountries, connectionSettings, countrySettings}, + {...configuration, appState: SequraFE.appStates.SETTINGS} + ); + + form?.render(); + } + + /** + * Saves data to data store if fetched on render. + * + * @param generalSettings + * @param shopCategories + * @param sellingCountries + */ + const saveFetchedDataToDataStore = (generalSettings, shopCategories, sellingCountries) => { + if (!SequraFE.state.getData('generalSettings')) { + SequraFE.state.setData('generalSettings', generalSettings) + } + + if (!SequraFE.state.getData('shopCategories')) { + SequraFE.state.setData('shopCategories', shopCategories) + } + + if (!SequraFE.state.getData('sellingCountries')) { + SequraFE.state.setData('sellingCountries', sellingCountries) + } + } + + /** + * Get sidebar link options. + * + * @returns {unknown[]} + */ + const getLinkConfiguration = () => { + return SequraFE.pages.settings.map((link) => { + const activePage = SequraFE.state.getPage() ?? SequraFE.pages.settings[0] + switch (link) { + case SequraFE.appPages.SETTINGS.GENERAL: + return { + label: 'sidebar.generalSettings', + href: '#settings-general', + icon: 'general', + isActive: activePage === SequraFE.appPages.SETTINGS.GENERAL + } + case SequraFE.appPages.SETTINGS.CONNECTION: + return { + label: 'sidebar.connectionSettings', + href: '#settings-connection', + icon: 'connection', + isActive: activePage === SequraFE.appPages.SETTINGS.CONNECTION + } + case SequraFE.appPages.SETTINGS.ORDER_STATUS: + return { + label: 'sidebar.orderStatusSettings', + href: '#settings-order_status', + icon: 'order', + isActive: activePage === SequraFE.appPages.SETTINGS.ORDER_STATUS + } + case SequraFE.appPages.SETTINGS.WIDGET: + return { + label: 'sidebar.widgetSettings', + href: '#settings-widget', + icon: 'widget', + isActive: activePage === SequraFE.appPages.SETTINGS.WIDGET + } + } + }); + } + + /** + * Initializes general settings state content. + */ + const initializePage = () => { + const pageWrapper = document.getElementById('sq-page-wrapper') + + pageWrapper.append( + generator.createElement('div', 'sq-page-content-wrapper sqv--settings', '', null, [ + SequraFE.components.PageHeader.create( + { + currentVersion: version?.current, + newVersion: version?.new && version?.downloadNewVersionUrl ? { + versionLabel: version.new, + versionUrl: version.downloadNewVersionUrl + } : null, + mode: connectionSettings.environment === 'live' ? connectionSettings.environment : 'test', + activeStore: currentStoreId, + stores: stores.map((store) => ({label: store.storeName, value: store.storeId})), + onChange: (storeId) => { + if (storeId !== SequraFE.state.getStoreId()) { + SequraFE.state.setStoreId(storeId); + window.location.hash = ''; + SequraFE.state.display(); + } + }, + menuItems: SequraFE.utilities.getMenuItems(SequraFE.appStates.SETTINGS) + } + ), + generator.createElement('div', 'sq-page-content', '', null, [getSidebarRow()]), + generator.createSupportLink() + ])) + } + + const getSidebarRow = () => { + return generator.createElement('div', 'sq-content-row', '', null, [ + generator.createSettingsSidebar({links: getLinkConfiguration()}), + generator.createElement('main', 'sq-content') + ]); + } + } + + SequraFE.SettingsController = SettingsController; +})(); diff --git a/view/adminhtml/web/js/override/StateController.js b/view/adminhtml/web/js/override/StateController.js new file mode 100644 index 00000000..ed1124a3 --- /dev/null +++ b/view/adminhtml/web/js/override/StateController.js @@ -0,0 +1,474 @@ +if (!window.SequraFE) { + window.SequraFE = {}; +} + +// Define the supported configuration capabilities. +SequraFE.flags = { + isShowCheckoutAsHostedPageFieldVisible: true, + configurableSelectorsForMiniWidgets: false, + isServiceSellingAllowed: false, + ...(SequraFE.flags || {}) +}; + +SequraFE.appStates = { + ONBOARDING: 'onboarding', + SETTINGS: 'settings', + PAYMENT: 'payment', + ADVANCED: 'advanced' +}; + +SequraFE.appPages = { + ONBOARDING: { + CONNECT: 'connect', + DEPLOYMENTS: 'deployments', + COUNTRIES: 'countries', + WIDGETS: 'widgets' + }, + SETTINGS: { + GENERAL: 'general', + CONNECTION: 'connection', + ORDER_STATUS: 'order_status', + WIDGET: 'widget' + }, + PAYMENT: { + METHODS: 'methods' + }, + ADVANCED: { + DEBUG: 'debug' + } +}; + +(function () { + /** + * @typedef Store + * @property {string} storeId + * @property {string} storeName + */ + + /** + * @typedef StateConfiguration + * @property {string} stateUrl + * @property {string} storesUrl + * @property {string} currentStoreUrl + * @property {string} getConnectionDataUrl + * @property {string} versionUrl + * @property {string} shopNameUrl + * @property {Record} pageConfiguration + * @property {string} [getDeploymentsUrl] + */ + + /** + * @typedef Version + * @property {string} current + * @property {string | null} new + * @property {string | null} downloadNewVersionUrl + */ + + /** + * @typedef ShopName + * @property {string} shopName + */ + + /** + * @typedef DataStore + * @property {Version | null} version + * @property {Store[] | null} stores + * @property {ConnectionSettings | null} connectionSettings + * @property {CountrySettings | null} countrySettings + * @property {GeneralSettings | null} generalSettings + * @property {WidgetSettings | null} widgetSettings + * @property {PaymentMethod[] | null} paymentMethods + * @property {SellingCountry[] | null} sellingCountries + * @property {DeploymentSettings[] | null} deploymentsSettings + * @property {DeploymentSettings[] | null} notConnectedDeployments + * @property {LogsSettings | null} logsSettings + * @property {Category[] | null} shopCategories + */ + + /** + * @typedef {Object} DeploymentSettings + * @property {string} id + * @property {string} name + * @property {boolean} [active] + */ + + /** + * @typedef {Object} LogsSettings + * @property {boolean} enabled + * @property {int} level + */ + + /** + * Main controller of the application. + * + * @param {StateConfiguration} configuration + * + * @constructor + */ + function StateController(configuration) { + /** @type AjaxServiceType */ + const api = SequraFE.ajaxService; + const { pageControllerFactory, templateService, utilities } = SequraFE; + + let currentState = ''; + let previousState = ''; + + /** + * @type {DataStore} + */ + let dataStore; + + const clearDataStore = () => { + dataStore = { + version: null, + stores: null, + connectionSettings: null, + notConnectedDeployments: null, + deploymentsSettings: null, + countrySettings: null, + generalSettings: null, + widgetSettings: null, + paymentMethods: null, + sellingCountries: null, + shopCategories: null, + logsSettings: null + }; + } + + clearDataStore(); + + /** + * Main entry point for the application. + * Determines the current state and runs the start controller. + */ + this.display = () => { + utilities.showLoader(); + clearDataStore(); + templateService.clearMainPage(); + + window.addEventListener('hashchange', updateStateOnHashChange, false); + + api.get(!this.getStoreId() ? configuration.currentStoreUrl : configuration.storesUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), () => null, SequraFE.customHeader) + .then( + /** @param {Store|Store[]} response */ + (response) => { + const loadStore = (store) => { + this.setStoreId(store.storeId); + + return displayPageBasedOnState(); + }; + + let store = !Array.isArray(response) ? + response : + response.find((s) => s.storeId === this.getStoreId()); + + if (!store) { + // the active store is probably deleted, we need to switch to the default store + return api.get(configuration.currentStoreUrl, null, SequraFE.customHeader).then(loadStore); + } + + return loadStore(store); + } + ) + }; + + /** + * Updates the application state on a hash change. + */ + const updateStateOnHashChange = () => { + const state = window.location.hash.substring(1); + state && this.goToState(state); + }; + + /** + * Opens a specific page based on the current state. + */ + const displayPageBasedOnState = () => { + utilities.showLoader(); + + return Promise.all([ + api.get(configuration.versionUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.storesUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getConnectionDataUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getCountrySettingsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getWidgetSettingsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getDeploymentsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + api.get(configuration.pageConfiguration.onboarding.getNotConnectedDeploymentsUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader), + ]).then(([versionRes, storesRes, connectionSettingsRes, countrySettingsRes, widgetSettingsRes, deploymentsSettingsRes, notConnectedDeployments, logsSettingsRes]) => { + dataStore.version = versionRes; + dataStore.stores = storesRes; + dataStore.connectionSettings = connectionSettingsRes; + dataStore.countrySettings = countrySettingsRes; + dataStore.widgetSettings = widgetSettingsRes; + dataStore.deploymentsSettings = deploymentsSettingsRes; + dataStore.notConnectedDeployments = notConnectedDeployments; + dataStore.logsSettings = logsSettingsRes; + + return api.get(configuration.stateUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader); + }).then((stateRes) => { + if (SequraFE.state.getCredentialsChanged()) { + SequraFE.state.removeCredentialsChanged(); + } + + const page = this.getPage(); + if (stateRes.state === SequraFE.appStates.ONBOARDING) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + page, null, true); + + return; + } + + if (SequraFE.pages?.advanced?.includes(page)) { + this.goToState(SequraFE.appStates.ADVANCED + '-' + page, null, true) + return; + } + + if (!page || SequraFE.pages.payment?.includes(page)) { + this.goToState(SequraFE.appStates.PAYMENT + '-' + SequraFE.appPages.PAYMENT.METHODS, null, true) + + return; + } + + this.goToState(SequraFE.appStates.SETTINGS + '-' + page, null, true); + }).catch(() => { + }); + }; + + /** + * Navigates to a state. + * + * @param {string} state + * @param {Record | null?} additionalConfig + * @param {boolean} [force=false] + */ + this.goToState = (state, additionalConfig = null, force = false) => { + if ((currentState === state && !force)) { + return; + } + + utilities.showLoader(); + let [controllerName, page] = state.split('-'); + + if (controllerName === SequraFE.appStates.ONBOARDING) { + + // To skip Widgets Onboarding we need to make sure that Widgets are configured (styles set and at least one display option enabled) + const areWidgetsConfigured = dataStore.widgetSettings?.widgetStyles !== undefined + && (dataStore.widgetSettings?.displayWidgetOnProductPage + || dataStore.widgetSettings?.showInstallmentAmountInCartPage + || dataStore.widgetSettings?.showInstallmentAmountInProductListing + ); + if ( + dataStore.connectionSettings?.connectionData?.every(c => c.username && c.password) && + dataStore.countrySettings?.length && + areWidgetsConfigured && + !SequraFE.state.getCredentialsChanged() + ) { + currentState.split('-')[0] === SequraFE.appStates.ONBOARDING ? + this.goToState(SequraFE.appStates.PAYMENT + '-' + SequraFE.appPages.PAYMENT.METHODS) : + this.goToState(currentState, null, true); + + return; + } + + if (!page) { + page = SequraFE.appPages.ONBOARDING.CONNECT; + } + + switch (page) { + case SequraFE.appPages.ONBOARDING.COUNTRIES: + if (!dataStore.connectionSettings?.connectionData?.every(c => c.username)) { + page = SequraFE.appPages.ONBOARDING.CONNECT + } + + if (!dataStore.deploymentsSettings?.some(deployment => deployment.active === true)) { + page = SequraFE.appPages.DEPLOYMENTS; + } + + break; + case SequraFE.appPages.ONBOARDING.DEPLOYMENTS: + page = SequraFE.appPages.ONBOARDING.DEPLOYMENTS; + break; + case SequraFE.appPages.ONBOARDING.WIDGETS: + if (dataStore.countrySettings?.length === 0 || SequraFE.state.getCredentialsChanged()) { + page = SequraFE.appPages.ONBOARDING.COUNTRIES; + } + + if (!dataStore.connectionSettings?.connectionData?.every(c => c.username)) { + page = SequraFE.appPages.ONBOARDING.CONNECT; + } + + if (!dataStore.deploymentsSettings?.some(deployment => deployment.active === true)) { + page = SequraFE.appPages.DEPLOYMENTS; + } + + break; + default: + page = SequraFE.appPages.ONBOARDING.CONNECT; + + if (!dataStore.deploymentsSettings?.some(deployment => deployment.active === true)) { + page = SequraFE.appPages.DEPLOYMENTS; + } + } + + displayPage(controllerName + '-' + page, additionalConfig); + + return; + } + + if (!dataStore.connectionSettings?.connectionData?.every(c => c.username && c.password) || SequraFE.state.getCredentialsChanged()) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.CONNECT, additionalConfig, true); + return; + } + if (dataStore.countrySettings?.length === 0) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.COUNTRIES, additionalConfig, true); + return; + } + if ('undefined' === typeof dataStore.widgetSettings?.widgetStyles) { + this.goToState(SequraFE.appStates.ONBOARDING + '-' + SequraFE.appPages.ONBOARDING.WIDGETS, additionalConfig, true); + return; + } + + displayPage(state, additionalConfig); + }; + + const displayPage = (state, additionalConfig = null) => { + let [controllerName, page] = state.split('-'); + if (!Object.values(SequraFE.appStates).includes(controllerName)) { + SequraFE.state.display(); + } + + if (!page || !SequraFE.pages[controllerName]?.includes(page)) { + page = SequraFE.pages[controllerName]?.[0]; + state = page ? controllerName + '-' + page : controllerName; + } + + const config = { storeId: this.getStoreId(), ...(additionalConfig || {}) }; + const controller = pageControllerFactory.getInstance( + controllerName, + getControllerConfiguration(controllerName, page) + ); + + previousState = currentState; + currentState = state; + setPage(page); + + window.location.hash = state; + controller && controller.display(config); + } + + /** + * Gets controller configuration. + * + * @param {string} controllerName + * @param {string?} page + * + * @return {Record} + */ + const getControllerConfiguration = (controllerName, page) => { + let config = utilities.cloneObject(configuration.pageConfiguration[controllerName] || {}); + Object.keys(config).forEach((key) => { + config[key] = config[key].replace(encodeURIComponent('{storeId}'), encodeURIComponent(this.getStoreId())); + }); + page && (config.page = page); + + return config; + }; + + /** + * Sets the application page to local storage. + * + * @param {string} page + */ + const setPage = (page) => { + localStorage.setItem('sq-page', page); + } + + /** + * Gets the application page from local storage. + * + * @returns {string} + */ + this.getPage = () => { + if (window.location.hash) { + let page = window.location.hash.substring(1); + if (page) { + page = page.split('-')[1]; + if (page) { + setPage(page); + return page; + } + } + } + + return localStorage.getItem('sq-page'); + } + + /** + * Sets the credentials changed flag to local storage. + */ + this.setCredentialsChanged = () => { + SequraFE.state.setData('paymentMethods', null); + localStorage.setItem('sq-password-changed', '1'); + } + + /** + * Removes the credentials changed flag from local storage. + * + * @returns {string} + */ + this.removeCredentialsChanged = () => { + localStorage.removeItem('sq-password-changed'); + } + + /** + * Gets the credentials changed flag from local storage. + * + * @returns {string} + */ + this.getCredentialsChanged = () => { + return localStorage.getItem('sq-password-changed'); + } + + /** + * Sets the store ID to local storage. + * + * @param {string} storeId + */ + this.setStoreId = (storeId) => { + sessionStorage.setItem('sq-active-store-id', storeId); + }; + + /** + * Gets the store ID from local storage. + * + * @returns {string} + */ + this.getStoreId = () => { + return sessionStorage.getItem('sq-active-store-id'); + }; + + /** + * Returns a getVersion promise. + * + * @returns {Promise} + */ + this.getShopName = () => { + return api.get(configuration.shopNameUrl.replace(encodeURIComponent('{storeId}'), this.getStoreId()), null, SequraFE.customHeader); + }; + + this.getData = (key) => { + if (!Object.keys(dataStore).includes(key)) { + return null; + } + + return dataStore[key]; + } + + this.setData = (key, value) => { + if (Object.keys(dataStore).includes(key)) { + dataStore[key] = value; + } + } + } + + SequraFE.StateController = StateController; +})(); From e619fa2eb9dae1ba2d4f49f66b115d1851380ee5 Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Mon, 29 Dec 2025 08:51:12 +0100 Subject: [PATCH 05/16] Add button for webhook re-registration ISSUE: LIS-90 --- view/adminhtml/web/css/sequra-core.css | 4 + .../web/js/ConnectionSettingsForm.js | 85 ++++++++++++++++--- view/adminhtml/web/js/ElementGenerator.js | 15 ++++ view/adminhtml/web/js/ResponseService.js | 23 +++++ view/adminhtml/web/lang/en.json | 5 ++ 5 files changed, 121 insertions(+), 11 deletions(-) diff --git a/view/adminhtml/web/css/sequra-core.css b/view/adminhtml/web/css/sequra-core.css index 4dec1f8b..f4876954 100644 --- a/view/adminhtml/web/css/sequra-core.css +++ b/view/adminhtml/web/css/sequra-core.css @@ -1381,6 +1381,10 @@ #sequra-page .sq-field-wrapper.sqm--block.sqm--bellow-frame { margin-top: 30px; } +#sequra-page .sq-field-wrapper.sqm--block.sqm--bellow-frame .sqm--actions-bar { + display: flex; + gap: 6px; +} #sequra-page .sq-field-wrapper .items { display: flex; align-items: center; diff --git a/view/adminhtml/web/js/ConnectionSettingsForm.js b/view/adminhtml/web/js/ConnectionSettingsForm.js index f41d0822..4a1637f3 100644 --- a/view/adminhtml/web/js/ConnectionSettingsForm.js +++ b/view/adminhtml/web/js/ConnectionSettingsForm.js @@ -29,6 +29,7 @@ if (!window.SequraFE) { * connectUrl: string, * validateConnectionDataUrl: string, * disconnectUrl: string, + * reRegisterUrl: string, * page: string, * appState: string * }} configuration @@ -316,15 +317,31 @@ if (!window.SequraFE) { return; } - pageInnerContent?.append( - generator.createButtonField({ - className: 'sqm--block sqm--bellow-frame', - buttonType: 'danger', - buttonSize: 'medium', - buttonLabel: 'general.disconnect', - onClick: handleDisconnect - }) - ); + const reRegisterWebhooksButton = generator.createButton({ + type: 'secondary', + size: 'medium', + className: '', + onClick: handleReRegister, + label: 'Re-register webhooks' + }) + + const disconnectionButton = generator.createButton({ + type: 'danger', + size: 'medium', + className: '', + onClick: handleDisconnect, + label: 'general.disconnect' + }) + + const actionsBar = generator.createActionsBar( + 'sqm--block sqm--bellow-frame', + [ + reRegisterWebhooksButton, + disconnectionButton + ] + ) + + pageInnerContent?.append(actionsBar); pageContent?.append( generator.createPageFooter({ @@ -555,7 +572,7 @@ if (!window.SequraFE) { utilities.showLoader(); - api.post(configuration.disconnectUrl, createPayload(), SequraFE.customHeader) + api.post(configuration.disconnectUrl, createDisconnectionPayload(), SequraFE.customHeader) .then(() => SequraFE.state.display()) .finally(utilities.hideLoader); }) @@ -564,7 +581,37 @@ if (!window.SequraFE) { /** * Handles the disconnect button click. */ - const createPayload = () => { + const handleReRegister = () => { + utilities.showLoader(); + + api.post(configuration.reRegisterUrl, createReRegisterPayload(), SequraFE.customHeader) + .then((response) => { + if (response.isSuccessful) { + SequraFE.responseService.successHandler( + {successMessage: 'connection.webhookReRegistration.successMessage'} + ).catch(() => { + }); + } else { + SequraFE.responseService.errorHandler( + {errorMessage: 'connection.webhookReRegistration.errorMessage'} + ).catch(() => { + }); + } + + }) + .catch((error) => { + SequraFE.responseService.errorHandler( + {errorMessage: error} + ).catch(() => { + }); + }) + .finally(utilities.hideLoader); + } + + /** + * Creates connection payload. + */ + const createDisconnectionPayload = () => { const isFullDisconnect = activeDeployments.length <= 1; const deploymentId = activeDeploymentId; @@ -574,6 +621,22 @@ if (!window.SequraFE) { }; } + /** + * Creates payload for webhook re-registration. + */ + const createReRegisterPayload = () => { + const environment = changedSettings.environment ?? 'sandbox'; + const username = getSettingsForActiveDeployment(changedSettings).username ?? ''; + const password = getSettingsForActiveDeployment(changedSettings).password ?? ''; + const deployment = getSettingsForActiveDeployment(changedSettings).deployment ?? ''; + + return { + environment, + username, + password, + deployment + }; + } /** * Disables the form footer controls. diff --git a/view/adminhtml/web/js/ElementGenerator.js b/view/adminhtml/web/js/ElementGenerator.js index 417486ae..c4c7762d 100644 --- a/view/adminhtml/web/js/ElementGenerator.js +++ b/view/adminhtml/web/js/ElementGenerator.js @@ -438,6 +438,20 @@ if (!window.SequraFE) { return createFieldWrapper(buttonLink, label, description, '', error, ''); }; + + /** + * Adds bar with multiple action buttons. + * + * @returns HTMLElement + */ + const createActionsBar = (className, children) => { + const fieldWrapper = createElement('div', 'sq-field-wrapper ' + className) + const inputWrapper = createElement('div', 'sqm--actions-bar', '', null, children); + fieldWrapper.append(inputWrapper); + + return fieldWrapper; + }; + /** * Creates multi item selector wrapper around the provided multi item selector element. * @@ -691,6 +705,7 @@ if (!window.SequraFE) { createButtonField, createButtonLink, createButtonLinkField, + createActionsBar, createMultiItemSelectorField, createCountryField, createFormFields, diff --git a/view/adminhtml/web/js/ResponseService.js b/view/adminhtml/web/js/ResponseService.js index 6f578f7f..a6d261e6 100644 --- a/view/adminhtml/web/js/ResponseService.js +++ b/view/adminhtml/web/js/ResponseService.js @@ -36,6 +36,29 @@ if (!window.SequraFE) { return Promise.reject(response); }; + /** + * Handles a success response from the submit action. + * + * @param {{successMessage?: string}} response + * @returns {Promise} + */ + this.successHandler = (response) => { + const {utilities, templateService, elementGenerator} = SequraFE; + let container = document.querySelector('.sqp-flash-message-wrapper'); + if (!container) { + container = elementGenerator.createElement('div', 'sqp-flash-message-wrapper'); + templateService.getMainPage().prepend(container); + } + + templateService.clearComponent(container); + + if (response.successMessage) { + container.prepend(utilities.createFlashMessage(response.successMessage, 'success')); + } + + return Promise.reject(response); + }; + /** * Handles unauthorized response. * diff --git a/view/adminhtml/web/lang/en.json b/view/adminhtml/web/lang/en.json index 257ed2d7..dd55a36b 100644 --- a/view/adminhtml/web/lang/en.json +++ b/view/adminhtml/web/lang/en.json @@ -172,6 +172,11 @@ "deployments": { "manage": "Manage Deployment Targets" }, + "webhookReRegistration": { + "title": "Re-register webhooks", + "successMessage": "Webhooks were re-registered successfully.", + "errorMessage": "Webhooks were not re-registered successfully." + }, "username": { "label": "Username", "description": "You can find your username in the email sent by SeQura." From 4736696e796b2cccf7c2e1b7553df73b6d410057 Mon Sep 17 00:00:00 2001 From: Tamara Vukovic Date: Mon, 29 Dec 2025 08:52:48 +0100 Subject: [PATCH 06/16] Implement RegexProvider provider ISSUE: LIS-90 --- Block/Adminhtml/Configuration/Index.php | 21 ++++++++++++++++++- .../layout/sequra_configuration_index.xml | 1 - .../templates/configuration/index.phtml | 14 +++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Block/Adminhtml/Configuration/Index.php b/Block/Adminhtml/Configuration/Index.php index 3c0c3dd2..48060d6d 100644 --- a/Block/Adminhtml/Configuration/Index.php +++ b/Block/Adminhtml/Configuration/Index.php @@ -7,6 +7,7 @@ use Magento\Backend\Model\Auth\Session; use Magento\Framework\Exception\LocalizedException; use Sequra\Core\Helper\UrlHelper; +use SeQura\Core\Infrastructure\Utility\RegexProvider; class Index extends Template { @@ -18,6 +19,10 @@ class Index extends Template * @var Session */ private $authSession; + /** + * @var RegexProvider + */ + private $regexProvider; /** * Content constructor. @@ -25,9 +30,15 @@ class Index extends Template * @param Context $context * @param UrlHelper $urlHelper * @param Session $authSession + * @param RegexProvider $regexProvider * @param mixed[] $data */ - public function __construct(Context $context, UrlHelper $urlHelper, Session $authSession, array $data = []) + public function __construct( + Context $context, + UrlHelper $urlHelper, + Session $authSession, + RegexProvider $regexProvider, + array $data = []) { if (!is_iterable($data)) { throw new \InvalidArgumentException('Data must be iterable'); @@ -36,6 +47,7 @@ public function __construct(Context $context, UrlHelper $urlHelper, Session $aut $this->urlHelper = $urlHelper; $this->authSession = $authSession; + $this->regexProvider = $regexProvider; } /** @@ -109,4 +121,11 @@ public function getAdminLanguage(): string $user = $this->authSession->getUser(); return strtoupper($user ? substr($user->getInterfaceLocale(), 0, 2) : 'en'); } + + public function getRegexJson(): string + { + $regex = $this->regexProvider->toArray(); + + return json_encode($regex); + } } diff --git a/view/adminhtml/layout/sequra_configuration_index.xml b/view/adminhtml/layout/sequra_configuration_index.xml index e75b908f..5dd02e23 100644 --- a/view/adminhtml/layout/sequra_configuration_index.xml +++ b/view/adminhtml/layout/sequra_configuration_index.xml @@ -10,7 +10,6 @@ - diff --git a/view/adminhtml/templates/configuration/index.phtml b/view/adminhtml/templates/configuration/index.phtml index 0300a26b..e9adcc60 100644 --- a/view/adminhtml/templates/configuration/index.phtml +++ b/view/adminhtml/templates/configuration/index.phtml @@ -13,6 +13,17 @@ + + + +