From 3135bfc12a2d7323558723a819b227ebec5c776f Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Wed, 15 Sep 2021 20:12:34 +0530 Subject: [PATCH 01/19] Upgrade and refactor global theme. - Change public and admin frontend primary colours. - Change images. - Refactor and fix styling on public pages. - Remove CSS grid lib from public pages. - Update Buefy and fix broken component styles (modal, toast). --- frontend/package.json | 2 +- frontend/public/favicon.png | Bin 3705 -> 1942 bytes frontend/src/api/index.js | 2 + frontend/src/assets/favicon.png | Bin 3705 -> 1942 bytes frontend/src/assets/logo.png | Bin 2520 -> 2056 bytes frontend/src/assets/logo.svg | 132 +--------- frontend/src/assets/style.scss | 71 +++--- frontend/src/components/LogView.vue | 2 +- frontend/src/constants.js | 2 +- frontend/src/utils.js | 8 +- frontend/src/views/Campaigns.vue | 7 +- frontend/src/views/Dashboard.vue | 3 + frontend/yarn.lock | 18 +- static/email-templates/base.html | 4 +- static/email-templates/default.tpl | 4 +- static/public/static/favicon.png | Bin 3705 -> 1942 bytes static/public/static/logo.png | Bin 2520 -> 2056 bytes static/public/static/logo.svg | 136 +---------- static/public/static/style.css | 228 +++--------------- static/public/templates/index.html | 10 +- .../public/templates/subscription-form.html | 16 +- static/public/templates/subscription.html | 24 +- 22 files changed, 123 insertions(+), 546 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a5e58f6a6..9b635eaac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "axios": "^0.21.1", - "buefy": "^0.9.7", + "buefy": "^0.9.10", "c3": "^0.7.20", "codeflask": "^1.4.1", "core-js": "^3.12.1", diff --git a/frontend/public/favicon.png b/frontend/public/favicon.png index 8010001052ac0803b3ed4eb1e34a6b3286942fd9..0ca8f02b9c938198974e282ea6fdb55da5cd0db8 100644 GIT binary patch delta 1870 zcmV-U2eJ719F`A|U4I87NkleHYgkZ}geoDb+JKr?YF}E@ypc*crF{U@R3L#x)}(D(1%*V41a1|n27=dP zFVLo~(uWcg+X)oe-u0Y5j6c?9?e*T7S(5%FKX~W<&pr2O=6`;id#?~R$#flA(~9vN zkOoxi!PI~*13ocX>Ke!hc*BTX#FGqyOD=bc->rH#O7|oZs|oi`M3X4%0VjYZ1$9AA z;%O(xTYAEBafSpPcpPX9&F*Xl5PgBs*nVDayc&kV-~re;u!7jcE|l*9 z%YwVKD29Q3#DBaFj@~gG#87|$sIbX>1d(5W)&_AR0K=fWahmthE}o46y8vt+sK?74 z0{OC)K79TH@+c|iZ>0)N$yK%WCGjSDK17WNxC`_QiS*`Dg^Pa!+SSn4zZcP`ORc1= zD0^vkez@3AVsQaTxN$@ep==4{T={s3YgJo0v2v_f!GGcbNVsv(KLGcK;aU06SHNr} z<%};>YT-VhT@7%Uk6hyc-U&-Wraz{C)0|${WhItHE^- z6K>ry;91bPpM=0Cad z4QT6o0_8wB70V#vb)eaUq`o*Yuz%K(+@k z`hPES-UA$2`9T01izBu6eUJx$e_PuSoo=fifX#hh!aj|a!EKw?o7Q?;QcgF>udVeWNth0Re$&#-y9m3jod`-i{!`#RE3L|U z07Eie2V$4H7JWXHq0W{gD?dQ#uvE8@IDcDf$eLE{a)Vhkd(g7-@QCR_?D_&h;;qKt zv9aRSlBc&((Ajbsc+E%4<^#;^t zrKNa!(W=rYFeZbaV<-{1)iAR|TM9!rwfCQk?~BD2nN}O@5+9*#Oooh&Pt~RG41fKf zVX%x`*p7t25(~{$1N_%QxSWJ*(|1I}hDzIy*sZ3}TB>sGcG?h-s%Nol{Pb#eDkWBM=X8-gO4f~TGtt)%eBRRBJ0N%Z+1fJjaQ~;M(7%Fy+R}ncs<|2R_XR{>2;kCZK!0EHzsZPs z=-e_Yyb}Q$56E;|X96%9+mF?fa+=jp)lK590FD(Q;eL`>>DUVrl`a6lrx9hKh%tZ0h&5S}MHvA^W4vt+uCoY?`o6WgI~ zF3O{HH6PCZslWM$#&QBI9P$VlXipZ1x z!Q+n{f1+mev0}&Oi>Jj?&NzctzJ*GVsd9M{Jwnas!yH(fEn0NZM%&fU-?s;qX977F zEsF3QFFHS-JKW7j>3^BjO>P3=AZYXppVAmX^*al7c!jXMG?l*HKYL60^8kf8jpsd9 zXq+!b2-oI#4!Bv#(IL>Em;c&aJ`bH+MrC~GHKaK_rQqdqJB6)T2NzDo3xO9ObxkE<`VZr~;5r z;%qGvZv~oA8i9KJPi4;ac}9RC;37f>PhDd1Q*<_5iPF9Q0e-VRmk`h;Bme*a07*qo IM6N<$g65u{KL7v# delta 3647 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 Rr_ulb002ovPDHLkV1mGm{jdN4 diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 931041acd..0d8827754 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -67,6 +67,8 @@ http.interceptors.response.use((resp) => { message: msg, type: 'is-danger', queue: false, + position: 'is-top', + pauseOnHover: true, }); } diff --git a/frontend/src/assets/favicon.png b/frontend/src/assets/favicon.png index 8010001052ac0803b3ed4eb1e34a6b3286942fd9..0ca8f02b9c938198974e282ea6fdb55da5cd0db8 100644 GIT binary patch delta 1870 zcmV-U2eJ719F`A|U4I87NkleHYgkZ}geoDb+JKr?YF}E@ypc*crF{U@R3L#x)}(D(1%*V41a1|n27=dP zFVLo~(uWcg+X)oe-u0Y5j6c?9?e*T7S(5%FKX~W<&pr2O=6`;id#?~R$#flA(~9vN zkOoxi!PI~*13ocX>Ke!hc*BTX#FGqyOD=bc->rH#O7|oZs|oi`M3X4%0VjYZ1$9AA z;%O(xTYAEBafSpPcpPX9&F*Xl5PgBs*nVDayc&kV-~re;u!7jcE|l*9 z%YwVKD29Q3#DBaFj@~gG#87|$sIbX>1d(5W)&_AR0K=fWahmthE}o46y8vt+sK?74 z0{OC)K79TH@+c|iZ>0)N$yK%WCGjSDK17WNxC`_QiS*`Dg^Pa!+SSn4zZcP`ORc1= zD0^vkez@3AVsQaTxN$@ep==4{T={s3YgJo0v2v_f!GGcbNVsv(KLGcK;aU06SHNr} z<%};>YT-VhT@7%Uk6hyc-U&-Wraz{C)0|${WhItHE^- z6K>ry;91bPpM=0Cad z4QT6o0_8wB70V#vb)eaUq`o*Yuz%K(+@k z`hPES-UA$2`9T01izBu6eUJx$e_PuSoo=fifX#hh!aj|a!EKw?o7Q?;QcgF>udVeWNth0Re$&#-y9m3jod`-i{!`#RE3L|U z07Eie2V$4H7JWXHq0W{gD?dQ#uvE8@IDcDf$eLE{a)Vhkd(g7-@QCR_?D_&h;;qKt zv9aRSlBc&((Ajbsc+E%4<^#;^t zrKNa!(W=rYFeZbaV<-{1)iAR|TM9!rwfCQk?~BD2nN}O@5+9*#Oooh&Pt~RG41fKf zVX%x`*p7t25(~{$1N_%QxSWJ*(|1I}hDzIy*sZ3}TB>sGcG?h-s%Nol{Pb#eDkWBM=X8-gO4f~TGtt)%eBRRBJ0N%Z+1fJjaQ~;M(7%Fy+R}ncs<|2R_XR{>2;kCZK!0EHzsZPs z=-e_Yyb}Q$56E;|X96%9+mF?fa+=jp)lK590FD(Q;eL`>>DUVrl`a6lrx9hKh%tZ0h&5S}MHvA^W4vt+uCoY?`o6WgI~ zF3O{HH6PCZslWM$#&QBI9P$VlXipZ1x z!Q+n{f1+mev0}&Oi>Jj?&NzctzJ*GVsd9M{Jwnas!yH(fEn0NZM%&fU-?s;qX977F zEsF3QFFHS-JKW7j>3^BjO>P3=AZYXppVAmX^*al7c!jXMG?l*HKYL60^8kf8jpsd9 zXq+!b2-oI#4!Bv#(IL>Em;c&aJ`bH+MrC~GHKaK_rQqdqJB6)T2NzDo3xO9ObxkE<`VZr~;5r z;%qGvZv~oA8i9KJPi4;ac}9RC;37f>PhDd1Q*<_5iPF9Q0e-VRmk`h;Bme*a07*qo IM6N<$g65u{KL7v# delta 3647 zcmV-F4#4r25BVIBU4IS+Nkl1Ym8mhb^g9}?p)m9Cw_nsKN99)6Dv*Bq-yI% zii3!Y`Qgw86jgCcgMxz}aY9=qKZ=CaL{_Z6Y>&Z!A|Q`eRh?E%_yM5=k{>Z@l`4g5 zVvjvGC~DP$ZR`mKJTv#KuRr$Q=j<~NV|(V>X}dJ{p4Z%S)_?cywbx#IuXTtn%e}oX zu4rN28W5`p_6z7UYO#bxgk22)g1<>()WtIdM&vqbrb9w;aB!smL^t(gH*UJU_s|EO zxWix{6>OCe00}?@IP+7Lz9^{rJM7YPM*H^QNdHgfTGu%v;EO8{FL-MLcUxhf5;$Na z0FV)UJcFIfdK%JO8_)1$G3dm#N!VQtv)*!de54G`)(MzX5y-z%|>h@ zZ4nqi$1gAgpOXNn-V3Tks2O+wk|Gh23_-5)41q_i)84=PmG_*PjsCMBK-}JY)CJMuY#rlaP!fX`Fq?C7$fLJ1|$jIAVPBKpF4)l`;QD9?>YgW z>pOf0VY_KIKVY;RJS))YV+iK2#KZZWU5^Ml6+{-G-b z?11c4-+$4aAn&!<>PAD@f@FcJ3s``ecLiATh%=C&+6p99mQVo)B(GauD&Y#}T#m4n zRQf~9jt|^_pcnLmifxfKMc z24;R>hH;Ro|1;_|H3icKNH3oEnsW! z@Nb*h<7T0?o8VIyFj!$AEm*iE-w?t-Mt@S6`+*DU0-^P5hv$(C!995X;1(QSCJPAG z;40xBEFpX(sk~?3$f^SuOu&}jp}rP-en7JY4e(Y}`!vFecx{pu07jd}7gXR$5gG+~ zIwD^padFlEr|J7%2FFl&{W3U2_e6p+;8RE#RZ)Iq-|(uJCu5z`1$IDo%8ouv+JAf+ zVMc`RZqj>GO9#hTwYmi-Xq+JNeFLAB;oxX-^2m-SuYW6l?w_su@2gv_Yx?O} zQ%WBraI;)JVrXena3_HhCW`~36EFdQ!(m~vr4VhE1So|VbYWQ*`vI-5LhhL?U~BL2 zT`jXc%v-!L#%*wbIrum#fT`7EH-88{;v~U+WxM^@A3w>H(^=b0vUk0fJI3#JX`7c= z8b-U&9lR7Em#a4OWC*L3Ff{MgKv<{%4e)!My!haw$9qT9{Tp3i;;Np_1?`$TwvRzh5kvdb!id2TbMV0tn%RGlcGrDR z_q;G0;7Pvp-Q{lp_|jkAHhzGxZ@~TCuwcesf<`Dbqf<1o5Lk*VC{qEjLy@8pkS%MY zvl+lw)BP(8_~OdL3*IjI?|){+5-SXa8O(~*vMj7X+E0MVA_GsF@h2^7-@g4Rc4ZZ3 z|2nOB+s41+rQV;$d@mhR+3W%xN0}U9=mILM|Mk~9?TPoL^#n^df4gI!HWrIRY@Zxj z3~y7A6Ph?2le8i_NrGnlx0an<-!1R}u=yFj)?)j&%=l7#CgNxl9e;70h-D*Ea?U9* zBWy`=uJs4$o)rN!Yz&JTnFnVxun9wdBBF@nKWT;itYz(wZ+~j(&$|Ua$@ZuCbGxej zaWnf*OS4Z4a}mquB1Jwgsb{DWR+1qFE>Dfq&qB@8sjrTaw zJ~z7aa)VD&;Ov*J!0WkA)9ajqGNv_2lKdzD*uA|kt{}LoHh+IHt>je9C35nchAR)w zcATG$1fRowm8GV6$P}F?VIx4;dp8XXFS8crt*O@V0yh;?@pQFCzRF{qDBJDFt`ztr zzvS)5Xzb)O&s^smO9Lapwrh=nSk(j{S8rnwO{T>R0Ilv4P z5dDB?EP4$mk`J?QcI(;R* z|6~ceD5l7A?eo1vgzA&xd?Pr2>c7_~INe3@jk!>KE``i=Vz!wCssRI>Z}@*MCa5xH zO}?t^Tz-*S&}F?YUZjQfezutessRH!YXGvfIacfPlYi38-f+o-YhRm>Ul7@~;lu)g zi^5T^eSf}}S~FV{WFX*Vo|Me#t8;ZBZb&i3BLMas!$L>$C}Fn@0~>@elyQ0RJ8^<3Es5g0LVs6uA@jbIq7^3 znCYH%su~bV>0_4}=ZxaeEmcCL$|@u3KB?7#lz)MhP>z_H4mHbCyw1-jz%e@uI8@)~ z=Dq8+{K~-&vVWacNLX7LXviPeI(w=$KwWhL#AvBFWI}N;<{dK(kMG9VR-Lb4a zvG7l>6al3s9*|>6uG927$7|6FBG%`Cv?OMBpsKk^45qS_l$@+gO!6{am)x=CuWp_A zjmr&w&#e<5cGsp`)e1QI@`ZaE8Z@~KO@&xIhH8-pNlDwD7`9~QGa0W-o3|G z{%!u8DFguG=lYI5ZDzklBa3P4gY05UWI7<8FUWtA<6wL2%#q1UYP&X^SkN9@)K9}2 zm)1#GD_1KFOwl1%&R12f(4+#0DRn|+@C$M;Ul<%2xDD@AA_D-M2aep>a{kba3%gPz z1%lKWfM){bS_0>ydIr~wL4WFT{?dx1l5u}(_>0Bq|WzMW=I{9h=O zgYbKUBmGZK_skgy0Dx^b4(}`q+rnh_-7l1^Vs{`bluVyAwokGGm8n*^2CroL@;!qi zD<7B%<9P`H#BIGtA1cg#_kU%ntn0wiV|}09ajB)U0QKs;q0e7?u7AC>0M`K??nDz% z1`q;RP*O=#MS=u^o~0z4#nQ>z8{(nS5u@!u#B^400mahpdU|Z}=$7+=&)eXADVb4Y!^fT5G%)fhyRpTY@3r8Ci17} zYj;SiPm9zGw7q6r$msdAvJ_saS7ziI z6|ik4Z$D=Q0Km4Dhu?#;*k2I8j%W?d+8o5{jLY@Xl)q4PeDieUd{+pFDsy}9kxz>J zveA}D*FP!vA-eh6oZzRu#IDQ!+FUN1ZalWAjm~B(_*uf1RDUV@RIAgdK8cTvS=y!x zpqeCvZF4mzTz2{El0Qrq59bq`OOaUM=hH@fO600;ygEzyVF2H3ziZJG zfBw>SZ_b6@b4EZeTl$ZDz})X3?Ph{EC7*yxdU@K1AU$u61AAUx_0nAH+7$wF*)%Y` ztnFF@VHHvIi#X~du!O`Sh@m)xdQ-xvfYTI5gz_j^9P(D@;Dd*6I@L}6{s+eb^HfC7 Rr_ulb002ovPDHLkV1mGm{jdN4 diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png index a90d9ffac0f2cf7a5f0cf073ee143cb1be4253bf..4697ced973de05db47b43a7bb0f8dc1f2168d2ed 100644 GIT binary patch delta 1999 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 diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg index 403800230..d3d36e759 100644 --- a/frontend/src/assets/logo.svg +++ b/frontend/src/assets/logo.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss index 8d5bd00e7..0a81ca9fd 100644 --- a/frontend/src/assets/style.scss +++ b/frontend/src/assets/style.scss @@ -5,10 +5,10 @@ $body-family: "Inter", "Helvetica Neue", sans-serif; $body-size: 15px; $background: $white-bis; $body-background-color: $white-bis; -$primary: #7f2aff; -$green: #4caf50; +$primary: #0055d4; +$green: #0db35e; $turquoise: $green; -$red: #ff5722; +$red: #FF5722; $link: $primary; $input-placeholder-color: $grey-light; $grey-lightest: #eaeaea; @@ -27,8 +27,6 @@ $menu-item-active-color: $primary; /* Buefy */ $modal-background-background-color: rgba(0, 0, 0, .30); -$speed-slow: 25ms !default; -$speed-slower: 50ms !default; /* Import full Bulma and Buefy */ @import "~bulma"; @@ -92,14 +90,13 @@ section { } .box { - background: $white-bis; - box-shadow: 2px 2px 5px $white-ter; - border: 1px solid $grey-lightest; - - hr { + background: $white; + box-shadow: 2px 2px 0 #f3f3f3; + border: 1px solid #eee; +} + .box hr { background-color: #efefef; } -} .page-header { min-height: 60px; @@ -175,6 +172,11 @@ section { } } +/* Fix for sidebar jumping on modals */ +body.is-noscroll .b-sidebar { + top: 26px; +} + /* Global notices */ .global-notices { margin-bottom: 30px; @@ -297,7 +299,9 @@ section { } .input, .taginput .taginput-container.is-focusable, .textarea { - box-shadow: inset 2px 2px 0px $white-ter; + // box-shadow: inset 2px 2px 0px $white-ter; + box-shadow: 2px 2px 0 $white-ter; + border: 1px solid $grey-lighter; } /* Form fields */ @@ -317,11 +321,11 @@ section { } label { - color: $grey; + color: $grey-dark; } .help { - color: $grey-light; + color: $grey; } } .has-numberinput .field, .field.is-grouped { @@ -363,21 +367,21 @@ section { box-shadow: 1px 1px 0 lighten($color, 37%); } &.public, &.running { - $color: #1890ff; + $color: $primary; color: $color; background: #e6f7ff; border: 1px solid lighten($color, 37%); box-shadow: 1px 1px 0 lighten($color, 25%); } &.finished, &.enabled { - $color: #50ab24; + $color: $green; color: $color; background: #f6ffed; border: 1px solid lighten($color, 45%); box-shadow: 1px 1px 0 lighten($color, 45%); } &.blocklisted, &.cancelled { - $color: #f5222d; + $color: $red; color: $color; background: #fff1f0; border: 1px solid lighten($color, 30%); @@ -406,6 +410,11 @@ section.dashboard { margin-bottom: 0.5rem; } + .tile.notification { + @extend .box; + padding: 10px; + } + .counts .column { padding: 30px; } @@ -488,6 +497,9 @@ section.campaigns { border-bottom: 1px solid lighten(#1890ff, 30%); } + .spinner { + margin-left: 10px; + } .spinner .loading-overlay .loading-icon::after { border-bottom-color: lighten(#1890ff, 30%); border-left-color: lighten(#1890ff, 30%); @@ -723,20 +735,23 @@ section.campaign { /* Toasts */ .notices { - @keyframes scale { - 0% { - scale: 1; - } - 50% { - scale: 1.3; + .toast { + @extend .box; + border-left: 15px solid $grey; + border-radius: 3px; + padding: 20px; + + &.is-danger { + background: $white; + border-left-color: $red; + color: $grey-dark; } - 100% { - scale: 1; + &.is-success { + background: $white; + border-left-color: $green; + color: $grey-dark; } } - .toast { - animation: scale 300ms ease-in-out; - } } @media screen and (max-width: 1450px) and (min-width: 769px) { diff --git a/frontend/src/components/LogView.vue b/frontend/src/components/LogView.vue index b7de05bbf..745e318c4 100644 --- a/frontend/src/components/LogView.vue +++ b/frontend/src/components/LogView.vue @@ -5,7 +5,7 @@ diff --git a/frontend/src/constants.js b/frontend/src/constants.js index afe4f1cd1..83a32eef9 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -29,7 +29,7 @@ export const storeKeys = Object.freeze({ export const timestamp = 'ddd D MMM YYYY, hh:mm A'; export const colors = Object.freeze({ - primary: '#7f2aff', + primary: '#0055d4', }); export const regDuration = '[0-9]+(ms|s|m|h|d)'; diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 47e6b0f98..d102fe26a 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -84,7 +84,7 @@ export default class Utils { // UI shortcuts. confirm = (msg, onConfirm, onCancel) => { Dialog.confirm({ - scroll: 'clip', + scroll: 'keep', message: !msg ? this.i18n.t('globals.messages.confirm') : msg, confirmText: this.i18n.t('globals.buttons.ok'), cancelText: this.i18n.t('globals.buttons.cancel'), @@ -95,7 +95,7 @@ export default class Utils { prompt = (msg, inputAttrs, onConfirm, onCancel) => { Dialog.prompt({ - scroll: 'clip', + scroll: 'keep', message: msg, confirmText: this.i18n.t('globals.buttons.ok'), cancelText: this.i18n.t('globals.buttons.cancel'), @@ -115,7 +115,9 @@ export default class Utils { message: this.escapeHTML(msg), type: !typ ? 'is-success' : typ, queue: false, - duration: duration || 2000, + duration: duration || 3000, + position: 'is-top', + pauseOnHover: true, }); }; diff --git a/frontend/src/views/Campaigns.vue b/frontend/src/views/Campaigns.vue index 47575f817..65a364102 100644 --- a/frontend/src/views/Campaigns.vue +++ b/frontend/src/views/Campaigns.vue @@ -124,7 +124,7 @@

- + {{ stats.rate.toFixed(0) }} / min @@ -192,8 +192,9 @@ - + diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index 460eb51b6..c3cb2d521 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -55,6 +55,9 @@

  • {{ status }} + + +
diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ba45397fd..e6d866ccc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2571,12 +2571,12 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4 escalade "^3.1.1" node-releases "^1.1.71" -buefy@^0.9.7: - version "0.9.7" - resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.7.tgz#694e73fe0b32632a53d94c5ba9cfa4468363badd" - integrity sha512-Fli0ZjNDgtFtHm0LItWmfhNJ1oLjDwPzUWccvwXXoo2mADXaH8JQxyhY+drUuUV5/GMu5PtwqQSqPgZy942VZg== +buefy@^0.9.10: + version "0.9.10" + resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.10.tgz#17f64ee1ba43a145d1d3c56f45cba95e4e2975fa" + integrity sha512-xXEoy/NTgBNiIfBTCdHi2Vu5SJJdB046py6ekUvYuUgYwRvulySZksdecVNNWdfEVU8iD4esZaRbTLwCegFcVQ== dependencies: - bulma "0.9.2" + bulma "0.9.3" buffer-crc32@~0.2.3: version "0.2.13" @@ -2617,10 +2617,10 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bulma@0.9.2: - version "0.9.2" - resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.2.tgz#340011e119c605f19b8ca886bfea595f1deaf23c" - integrity sha512-e14EF+3VSZ488yL/lJH0tR8mFWiEQVCMi/BQUMi2TGMBOk+zrDg4wryuwm/+dRSHJw0gMawp2tsW7X1JYUCE3A== +bulma@0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.3.tgz#ddccb7436ebe3e21bf47afe01d3c43a296b70243" + integrity sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g== bytes@3.0.0: version "3.0.0" diff --git a/static/email-templates/base.html b/static/email-templates/base.html index 8dfcc01e8..d1434083d 100644 --- a/static/email-templates/base.html +++ b/static/email-templates/base.html @@ -43,7 +43,7 @@ padding: 30px; } .button { - background: #7f2aff; + background: #0055d4; color: #fff !important; display: inline-block; border-radius: 3px; @@ -61,7 +61,7 @@ } a { - color: #7f2aff; + color: #0055d4; } a:hover { color: #111; diff --git a/static/email-templates/default.tpl b/static/email-templates/default.tpl index 82653b1d3..72d5da96e 100644 --- a/static/email-templates/default.tpl +++ b/static/email-templates/default.tpl @@ -24,7 +24,7 @@ } .button { - background: #7f2aff; + background: #0055d4; border-radius: 3px; text-decoration: none !important; color: #fff !important; @@ -54,7 +54,7 @@ } a { - color: #7f2aff; + color: #0055d4; } a:hover { color: #111; diff --git a/static/public/static/favicon.png b/static/public/static/favicon.png index 8010001052ac0803b3ed4eb1e34a6b3286942fd9..0ca8f02b9c938198974e282ea6fdb55da5cd0db8 100644 GIT binary patch delta 1870 zcmV-U2eJ719F`A|U4I87NkleHYgkZ}geoDb+JKr?YF}E@ypc*crF{U@R3L#x)}(D(1%*V41a1|n27=dP zFVLo~(uWcg+X)oe-u0Y5j6c?9?e*T7S(5%FKX~W<&pr2O=6`;id#?~R$#flA(~9vN zkOoxi!PI~*13ocX>Ke!hc*BTX#FGqyOD=bc->rH#O7|oZs|oi`M3X4%0VjYZ1$9AA z;%O(xTYAEBafSpPcpPX9&F*Xl5PgBs*nVDayc&kV-~re;u!7jcE|l*9 z%YwVKD29Q3#DBaFj@~gG#87|$sIbX>1d(5W)&_AR0K=fWahmthE}o46y8vt+sK?74 z0{OC)K79TH@+c|iZ>0)N$yK%WCGjSDK17WNxC`_QiS*`Dg^Pa!+SSn4zZcP`ORc1= zD0^vkez@3AVsQaTxN$@ep==4{T={s3YgJo0v2v_f!GGcbNVsv(KLGcK;aU06SHNr} z<%};>YT-VhT@7%Uk6hyc-U&-Wraz{C)0|${WhItHE^- z6K>ry;91bPpM=0Cad z4QT6o0_8wB70V#vb)eaUq`o*Yuz%K(+@k z`hPES-UA$2`9T01izBu6eUJx$e_PuSoo=fifX#hh!aj|a!EKw?o7Q?;QcgF>udVeWNth0Re$&#-y9m3jod`-i{!`#RE3L|U z07Eie2V$4H7JWXHq0W{gD?dQ#uvE8@IDcDf$eLE{a)Vhkd(g7-@QCR_?D_&h;;qKt zv9aRSlBc&((Ajbsc+E%4<^#;^t zrKNa!(W=rYFeZbaV<-{1)iAR|TM9!rwfCQk?~BD2nN}O@5+9*#Oooh&Pt~RG41fKf zVX%x`*p7t25(~{$1N_%QxSWJ*(|1I}hDzIy*sZ3}TB>sGcG?h-s%Nol{Pb#eDkWBM=X8-gO4f~TGtt)%eBRRBJ0N%Z+1fJjaQ~;M(7%Fy+R}ncs<|2R_XR{>2;kCZK!0EHzsZPs z=-e_Yyb}Q$56E;|X96%9+mF?fa+=jp)lK590FD(Q;eL`>>DUVrl`a6lrx9hKh%tZ0h&5S}MHvA^W4vt+uCoY?`o6WgI~ zF3O{HH6PCZslWM$#&QBI9P$VlXipZ1x z!Q+n{f1+mev0}&Oi>Jj?&NzctzJ*GVsd9M{Jwnas!yH(fEn0NZM%&fU-?s;qX977F zEsF3QFFHS-JKW7j>3^BjO>P3=AZYXppVAmX^*al7c!jXMG?l*HKYL60^8kf8jpsd9 zXq+!b2-oI#4!Bv#(IL>Em;c&aJ`bH+MrC~GHKaK_rQqdqJB6)T2NzDo3xO9ObxkE<`VZr~;5r z;%qGvZv~oA8i9KJPi4;ac}9RC;37f>PhDd1Q*<_5iPF9Q0e-VRmk`h;Bme*a07*qo IM6N<$g65u{KL7v# delta 3647 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 Rr_ulb002ovPDHLkV1mGm{jdN4 diff --git a/static/public/static/logo.png b/static/public/static/logo.png index a90d9ffac0f2cf7a5f0cf073ee143cb1be4253bf..4697ced973de05db47b43a7bb0f8dc1f2168d2ed 100644 GIT binary patch delta 1999 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 diff --git a/static/public/static/logo.svg b/static/public/static/logo.svg index e0be10f19..d3d36e759 100644 --- a/static/public/static/logo.svg +++ b/static/public/static/logo.svg @@ -1,135 +1 @@ - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/static/public/static/style.css b/static/public/static/style.css index c3c901256..813c9a25d 100644 --- a/static/public/static/style.css +++ b/static/public/static/style.css @@ -1,184 +1,6 @@ * { box-sizing: border-box; } - -/* Flexit grid */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 10px; - box-sizing: border-box; -} -.row { - box-sizing: border-box; - display: flex; - flex: 0 1 auto; - flex-flow: row wrap; -} -.columns, -.column { - box-sizing: border-box; - flex-grow: 1; - flex-shrink: 1; - flex-basis: 1; - margin: 10px 0 10px 4%; -} -.column:first-child, -.columns:first-child { - margin-left: 0; -} -.one { - max-width: 4.6666666667%; -} -.two { - max-width: 13.3333333333%; -} -.three { - max-width: 22%; -} -.four { - max-width: 30.6666666667%; -} -.five { - max-width: 39.3333333333%; -} -.six { - max-width: 48%; -} -.seven { - max-width: 56.6666666667%; -} -.eight { - max-width: 65.3333333333%; -} -.nine { - max-width: 74%; -} -.ten { - max-width: 82.6666666667%; -} -.eleven { - max-width: 91.3333333333%; -} -.twelve { - max-width: 100%; - margin-left: 0; -} -.column-offset-0 { - margin-left: 0; -} -.column-offset-1 { - margin-left: 8.33333333%; -} -.column-offset-2 { - margin-left: 16.66666667%; -} -.column-offset-3 { - margin-left: 25%; -} -.column-offset-4 { - margin-left: 33.33333333%; -} -.column-offset-5 { - margin-left: 41.66666667%; -} -.column-offset-6 { - margin-left: 50%; -} -.column-offset-7 { - margin-left: 58.33333333%; -} -.column-offset-8 { - margin-left: 66.66666667%; -} -.column-offset-9 { - margin-left: 75%; -} -.column-offset-10 { - margin-left: 83.33333333%; -} -.column-offset-11 { - margin-left: 91.66666667%; -} -.between { - justify-content: space-between; -} -.evenly { - justify-content: space-evenly; -} -.around { - justify-content: space-around; -} -.center { - justify-content: center; - text-align: center; -} -.start { - justify-content: flex-start; -} -.end { - justify-content: flex-end; -} -.top { - align-items: flex-start; -} -.bottom { - align-items: flex-end; -} -.middle { - align-items: center; -} -.first { - order: -1; -} -.last { - order: 1; -} -.vertical { - flex-flow: column wrap; -} -.row-align-center { - align-items: center; -} -.space-right { - margin-right: 10px; -} -.space-left { - margin-left: 10px; -} -.space-bottom { - margin-bottom: 10px; -} -.space-top { - margin-top: 10px; -} -@media screen and (max-width: 768px) { - .container { - overflow: auto; - } - .columns, - .column { - min-width: 100%; - margin: 10px 0; - } - .column-offset-0, - .column-offset-1, - .column-offset-2, - .column-offset-3, - .column-offset-4, - .column-offset-5, - .column-offset-6, - .column-offset-7, - .column-offset-8, - .column-offset-9, - .column-offset-10, - .column-offset-11 { - margin: unset; - } -} /*# sourceMappingURL=dist/flexit.min.css.map */ - html, body { padding: 0; margin: 0; @@ -188,11 +10,11 @@ body { background: #f9f9f9; font-family: "Open Sans", "Helvetica Neue", sans-serif; font-size: 16px; - line-height: 28px; + line-height: 26px; color: #111; } a { - color: #7f2aff; + color: #0055d4; } a:hover { color: #111; @@ -216,14 +38,17 @@ input[type="text"], input[type="email"], select { border: 1px solid #888; border-radius: 3px; width: 100%; + box-shadow: 2px 2px 0 #f3f3f3; + border: 1px solid #ddd; + font-size: 1em; } input:focus { - border-color: #7f2aff; + border-color: #0055d4; } .button { - background: #7f2aff; - padding: 15px 30px; + background: #0055d4; + padding: 10px 30px; border-radius: 3px; border: 0; cursor: pointer; @@ -238,19 +63,22 @@ input[type="text"], input[type="email"], select { } .button.button-outline { background: #fff; - border: 1px solid #7f2aff; - color: #7f2aff; + border: 1px solid #0055d4; + color: #0055d4; } .button.button-outline:hover { - background-color: #7f2aff; + background-color: #0055d4; color: #fff; } +.container { + margin: 60px auto 15px auto; + max-width: 550px; +} + .wrap { background: #fff; - margin-top: 60px; - max-width: 600px; - padding: 45px; + padding: 40px; box-shadow: 2px 2px 0 #f3f3f3; border: 1px solid #eee; } @@ -271,27 +99,33 @@ input[type="text"], input[type="email"], select { border-top: 1px solid #eee; } +.row { + margin-bottom: 20px; +} .form .lists { margin-top: 45px; + list-style-type: none; + padding: 0; } .form .nonce { display: none; } -.footer { +footer.container { + margin-top: 15px; text-align: center; color: #aaa; font-size: 0.775em; margin-top: 30px; margin-bottom: 30px; } -.footer a { - color: #aaa; - text-decoration: none; -} -.footer a:hover { - color: #111; -} + footer a { + color: #aaa; + text-decoration: none; + } + footer a:hover { + color: #111; + } @media screen and (max-width: 650px) { .wrap { diff --git a/static/public/templates/index.html b/static/public/templates/index.html index 3de651a04..507476c66 100644 --- a/static/public/templates/index.html +++ b/static/public/templates/index.html @@ -6,8 +6,6 @@ {{ .Data.Title }} - - {{ if ne .FaviconURL "" }} @@ -32,11 +30,9 @@ {{ define "footer" }} -
- -
+ {{ end }} diff --git a/static/public/templates/subscription-form.html b/static/public/templates/subscription-form.html index 67db77299..d49695f66 100644 --- a/static/public/templates/subscription-form.html +++ b/static/public/templates/subscription-form.html @@ -15,19 +15,15 @@

{{ L.T "public.subTitle" }}

-
+

    {{ L.T "globals.terms.lists" }}

    {{ range $i, $l := .Data.Lists }} -
    -
    - -
    -
    - -
    -
    +
  • + + +
  • {{ end }} -
+

diff --git a/static/public/templates/subscription.html b/static/public/templates/subscription.html index f4e8a8e8e..923697139 100644 --- a/static/public/templates/subscription.html +++ b/static/public/templates/subscription.html @@ -25,27 +25,19 @@

{{ L.T "public.unsubTitle" }}

{{ L.T "public.privacyTitle" }}

{{ if .Data.AllowExport }}
-
- -
-
- -
- {{ L.T "public.privacyExportHelp" }} -
+ + +
+ {{ L.T "public.privacyExportHelp" }}
{{ end }} {{ if .Data.AllowWipe }}
-
- -
-
- -
- {{ L.T "public.privacyWipeHelp" }} -
+ + +
+ {{ L.T "public.privacyWipeHelp" }}
{{ end }}

From 3d0031b2073c4986d88fb7d4c16cbf0856f5d64d Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 11 Sep 2021 12:57:55 +0530 Subject: [PATCH 02/19] Add campaign analytics APIs and UI --- cmd/campaigns.go | 104 +++++- cmd/handlers.go | 1 + cmd/queries.go | 4 + cmd/subscribers.go | 6 +- frontend/src/App.vue | 4 + frontend/src/api/index.js | 12 + frontend/src/assets/style.scss | 86 +++-- frontend/src/router/index.js | 6 + frontend/src/utils.js | 17 + frontend/src/views/CampaignAnalytics.vue | 427 +++++++++++++++++++++++ frontend/src/views/Campaigns.vue | 41 ++- i18n/cs-cz.json | 2 +- i18n/de.json | 2 +- i18n/en.json | 10 +- i18n/es.json | 2 +- i18n/fr.json | 2 +- i18n/it.json | 2 +- i18n/ml.json | 2 +- i18n/pl.json | 2 +- i18n/pt-BR.json | 2 +- i18n/pt.json | 2 +- i18n/ru.json | 2 +- i18n/tr.json | 2 +- queries.sql | 37 ++ 24 files changed, 707 insertions(+), 70 deletions(-) create mode 100644 frontend/src/views/CampaignAnalytics.vue diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 312f79980..41576c555 100644 --- a/cmd/campaigns.go +++ b/cmd/campaigns.go @@ -14,6 +14,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/jmoiron/sqlx" "github.com/knadh/listmonk/internal/subimporter" "github.com/knadh/listmonk/models" "github.com/labstack/echo" @@ -49,6 +50,17 @@ type campaignContentReq struct { To string `json:"to"` } +type campCountStats struct { + CampaignID int `db:"campaign_id" json:"campaign_id"` + Count int `db:"count" json:"count"` + Timestamp time.Time `db:"timestamp" json:"timestamp"` +} + +type campTopLinks struct { + URL string `db:"url" json:"url"` + Count int `db:"count" json:"count"` +} + type campaignStats struct { ID int `db:"id" json:"id"` Status string `db:"status" json:"status"` @@ -96,23 +108,11 @@ func handleGetCampaigns(c echo.Context) error { if id > 0 { single = true } - if query != "" { - query = `%` + - string(regexFullTextQuery.ReplaceAll([]byte(query), []byte("&"))) + `%` - } - - // Sort params. - if !strSliceContains(orderBy, campaignQuerySortFields) { - orderBy = "created_at" - } - if order != sortAsc && order != sortDesc { - order = sortDesc - } - stmt := fmt.Sprintf(app.queries.QueryCampaigns, orderBy, order) + queryStr, stmt := makeCampaignQuery(query, orderBy, order, app.queries.QueryCampaigns) // Unsafe to ignore scanning fields not present in models.Campaigns. - if err := db.Select(&out.Results, stmt, id, pq.StringArray(status), query, pg.Offset, pg.Limit); err != nil { + if err := db.Select(&out.Results, stmt, id, pq.StringArray(status), queryStr, pg.Offset, pg.Limit); err != nil { app.log.Printf("error fetching campaigns: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, app.i18n.Ts("globals.messages.errorFetching", @@ -605,6 +605,64 @@ func handleTestCampaign(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } +// handleGetCampaignViewAnalytics retrieves view counts for a campaign. +func handleGetCampaignViewAnalytics(c echo.Context) error { + var ( + app = c.Get("app").(*App) + + typ = c.Param("type") + from = c.QueryParams().Get("from") + to = c.QueryParams().Get("to") + ) + + ids, err := parseStringIDs(c.Request().URL.Query()["id"]) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, + app.i18n.Ts("globals.messages.errorInvalidIDs", "error", err.Error())) + } + + if len(ids) == 0 { + return echo.NewHTTPError(http.StatusBadRequest, + app.i18n.Ts("globals.messages.missingFields", "name", "`id`")) + } + + // Pick campaign view counts or click counts. + var stmt *sqlx.Stmt + switch typ { + case "views": + stmt = app.queries.GetCampaignViewCounts + case "clicks": + stmt = app.queries.GetCampaignClickCounts + case "bounces": + stmt = app.queries.GetCampaignBounceCounts + case "links": + out := make([]campTopLinks, 0) + if err := app.queries.GetCampaignLinkCounts.Select(&out, pq.Int64Array(ids), from, to); err != nil { + app.log.Printf("error fetching campaign %s: %v", typ, err) + return echo.NewHTTPError(http.StatusInternalServerError, + app.i18n.Ts("globals.messages.errorFetching", + "name", "{globals.terms.analytics}", "error", pqErrMsg(err))) + } + return c.JSON(http.StatusOK, okResp{out}) + default: + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + if !strHasLen(from, 10, 30) || !strHasLen(to, 10, 30) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("analytics.invalidDates")) + } + + out := make([]campCountStats, 0) + if err := stmt.Select(&out, pq.Int64Array(ids), from, to); err != nil { + app.log.Printf("error fetching campaign %s: %v", typ, err) + return echo.NewHTTPError(http.StatusInternalServerError, + app.i18n.Ts("globals.messages.errorFetching", + "name", "{globals.terms.analytics}", "error", pqErrMsg(err))) + } + + return c.JSON(http.StatusOK, okResp{out}) +} + // sendTestMessage takes a campaign and a subsriber and sends out a sample campaign message. func sendTestMessage(sub models.Subscriber, camp *models.Campaign, app *App) error { if err := camp.CompileTemplate(app.manager.TemplateFuncs(camp)); err != nil { @@ -719,3 +777,21 @@ func makeOptinCampaignMessage(o campaignReq, app *App) (campaignReq, error) { o.Body = b.String() return o, nil } + +// makeCampaignQuery cleans an optional campaign search string and prepares the +// campaign SQL statement (string) and returns them. +func makeCampaignQuery(q, orderBy, order, query string) (string, string) { + if q != "" { + q = `%` + string(regexFullTextQuery.ReplaceAll([]byte(q), []byte("&"))) + `%` + } + + // Sort params. + if !strSliceContains(orderBy, campaignQuerySortFields) { + orderBy = "created_at" + } + if order != sortAsc && order != sortDesc { + order = sortDesc + } + + return q, fmt.Sprintf(query, orderBy, order) +} diff --git a/cmd/handlers.go b/cmd/handlers.go index 7e777ba0f..dd469234c 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -101,6 +101,7 @@ func registerHTTPHandlers(e *echo.Echo, app *App) { g.GET("/api/campaigns", handleGetCampaigns) g.GET("/api/campaigns/running/stats", handleGetRunningCampaignStats) g.GET("/api/campaigns/:id", handleGetCampaigns) + g.GET("/api/campaigns/analytics/:type", handleGetCampaignViewAnalytics) g.GET("/api/campaigns/:id/preview", handlePreviewCampaign) g.POST("/api/campaigns/:id/preview", handlePreviewCampaign) g.POST("/api/campaigns/:id/content", handleCampaignContent) diff --git a/cmd/queries.go b/cmd/queries.go index 3fdded4dd..ed55331bd 100644 --- a/cmd/queries.go +++ b/cmd/queries.go @@ -57,6 +57,10 @@ type Queries struct { GetCampaignForPreview *sqlx.Stmt `query:"get-campaign-for-preview"` GetCampaignStats *sqlx.Stmt `query:"get-campaign-stats"` GetCampaignStatus *sqlx.Stmt `query:"get-campaign-status"` + GetCampaignViewCounts *sqlx.Stmt `query:"get-campaign-view-counts"` + GetCampaignClickCounts *sqlx.Stmt `query:"get-campaign-click-counts"` + GetCampaignBounceCounts *sqlx.Stmt `query:"get-campaign-bounce-counts"` + GetCampaignLinkCounts *sqlx.Stmt `query:"get-campaign-link-counts"` NextCampaigns *sqlx.Stmt `query:"next-campaigns"` NextCampaignSubscribers *sqlx.Stmt `query:"next-campaign-subscribers"` GetOneCampaignSubscriber *sqlx.Stmt `query:"get-one-campaign-subscriber"` diff --git a/cmd/subscribers.go b/cmd/subscribers.go index a4a628857..00aa13f2d 100644 --- a/cmd/subscribers.go +++ b/cmd/subscribers.go @@ -409,7 +409,7 @@ func handleBlocklistSubscribers(c echo.Context) error { var req subQueryReq if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, - app.i18n.Ts("subscribers.errorInvalidIDs", "error", err.Error())) + app.i18n.Ts("globals.messages.errorInvalidIDs", "error", err.Error())) } if len(req.SubscriberIDs) == 0 { return echo.NewHTTPError(http.StatusBadRequest, @@ -449,7 +449,7 @@ func handleManageSubscriberLists(c echo.Context) error { var req subQueryReq if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, - app.i18n.Ts("subscribers.errorInvalidIDs", "error", err.Error())) + app.i18n.Ts("globals.messages.errorInvalidIDs", "error", err.Error())) } if len(req.SubscriberIDs) == 0 { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.errorNoIDs")) @@ -505,7 +505,7 @@ func handleDeleteSubscribers(c echo.Context) error { i, err := parseStringIDs(c.Request().URL.Query()["id"]) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, - app.i18n.Ts("subscribers.errorInvalidIDs", "error", err.Error())) + app.i18n.Ts("globals.messages.errorInvalidIDs", "error", err.Error())) } if len(i) == 0 { return echo.NewHTTPError(http.StatusBadRequest, diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 485d26a26..4cae158da 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -80,6 +80,10 @@ + + http.get('/api/campaigns/running/sta export const createCampaign = async (data) => http.post('/api/campaigns', data, { loading: models.campaigns }); +export const getCampaignViewCounts = async (params) => http.get('/api/campaigns/analytics/views', + { params, loading: models.campaigns }); + +export const getCampaignClickCounts = async (params) => http.get('/api/campaigns/analytics/clicks', + { params, loading: models.campaigns }); + +export const getCampaignBounceCounts = async (params) => http.get('/api/campaigns/analytics/bounces', + { params, loading: models.campaigns }); + +export const getCampaignLinkCounts = async (params) => http.get('/api/campaigns/analytics/links', + { params, loading: models.campaigns }); + export const convertCampaignContent = async (data) => http.post(`/api/campaigns/${data.id}/content`, data, { loading: models.campaigns }); diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss index 0a81ca9fd..05488172a 100644 --- a/frontend/src/assets/style.scss +++ b/frontend/src/assets/style.scss @@ -262,10 +262,17 @@ body.is-noscroll .b-sidebar { padding: 15px 10px; border-color: $grey-lightest; } + .actions a, .actions .a { margin: 0 10px; display: inline-block; } + .actions a[data-disabled], + .actions .icon[data-disabled] { + pointer-events: none; + cursor: not-allowed; + color: $grey-light; + } } /* Modal */ @@ -294,16 +301,28 @@ body.is-noscroll .b-sidebar { } } -.autocomplete .dropdown-content { - background-color: $white-ter; +.autocomplete { + .dropdown-content { + background-color: $white-bis; + } + a.dropdown-item { + &:hover, &.is-hovered { + background-color: $grey-lightest; + color: $primary; + } + } } .input, .taginput .taginput-container.is-focusable, .textarea { - // box-shadow: inset 2px 2px 0px $white-ter; box-shadow: 2px 2px 0 $white-ter; border: 1px solid $grey-lighter; } +.input { + height: auto; + padding: 10px; +} + /* Form fields */ .field { &:not(:last-child) { @@ -368,10 +387,10 @@ body.is-noscroll .b-sidebar { } &.public, &.running { $color: $primary; - color: $color; + color: lighten($color, 20%);; background: #e6f7ff; - border: 1px solid lighten($color, 37%); - box-shadow: 1px 1px 0 lighten($color, 25%); + border: 1px solid lighten($color, 42%); + box-shadow: 1px 1px 0 lighten($color, 42%); } &.finished, &.enabled { $color: $green; @@ -491,25 +510,22 @@ section.import { /* Campaigns page */ section.campaigns { table tbody { + .spinner { + margin-left: 10px; + .loading-overlay .loading-icon::after { + border-bottom-color: lighten(#1890ff, 30%); + border-left-color: lighten(#1890ff, 30%); + } + } + tr.running { background: lighten(#1890ff, 43%); td { border-bottom: 1px solid lighten(#1890ff, 30%); } - - .spinner { - margin-left: 10px; - } - .spinner .loading-overlay .loading-icon::after { - border-bottom-color: lighten(#1890ff, 30%); - border-left-color: lighten(#1890ff, 30%); - } } td { - &.status .spinner { - margin-left: 10px; - } .tags { margin-top: 5px; } @@ -519,15 +535,8 @@ section.campaigns { } &.lists ul { - font-size: $size-7; + // font-size: $size-7; list-style-type: circle; - - a { - color: $grey-dark; - &:hover { - color: $primary; - } - } } .fields { @@ -555,6 +564,26 @@ section.campaigns { } } +section.analytics { + .charts { + position: relative; + min-height: 100px; + } + + .chart { + margin-bottom: 45px; + } + + .donut-container { + position: relative; + } + .donut { + bottom: 0px; + right: 0px; + position: absolute !important; + } +} + /* Campaign / template preview popup */ .preview { padding: 0; @@ -702,11 +731,10 @@ section.campaign { } .c3-tooltip { - border: 0; - background-color: #fff; + @extend .box; + padding: 10px; empty-cells: show; - box-shadow: none; - opacity: 0.9; + opacity: 0.95; tr { border: 0; diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 724a28265..efb01a950 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -71,6 +71,12 @@ const routes = [ meta: { title: 'Templates', group: 'campaigns' }, component: () => import(/* webpackChunkName: "main" */ '../views/Templates.vue'), }, + { + path: '/campaigns/analytics', + name: 'campaignAnalytics', + meta: { title: 'Campaign analytics', group: 'campaigns' }, + component: () => import(/* webpackChunkName: "main" */ '../views/CampaignAnalytics.vue'), + }, { path: '/campaigns/:id', name: 'campaign', diff --git a/frontend/src/utils.js b/frontend/src/utils.js index d102fe26a..80816adf2 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -78,6 +78,23 @@ export default class Utils { return out.toFixed(2) + pfx; } + // Parse one or more numeric ids as query params and return as an array of ints. + parseQueryIDs = (ids) => { + if (!ids) { + return []; + } + + if (typeof ids === 'string') { + return [parseInt(ids, 10)]; + } + + if (typeof ids === 'number') { + return [parseInt(ids, 10)]; + } + + return ids.map((id) => parseInt(id, 10)); + } + // https://stackoverflow.com/a/12034334 escapeHTML = (html) => html.replace(/[&<>"'`=/]/g, (s) => htmlEntities[s]); diff --git a/frontend/src/views/CampaignAnalytics.vue b/frontend/src/views/CampaignAnalytics.vue new file mode 100644 index 000000000..83e1dc59d --- /dev/null +++ b/frontend/src/views/CampaignAnalytics.vue @@ -0,0 +1,427 @@ + + + + + diff --git a/frontend/src/views/Campaigns.vue b/frontend/src/views/Campaigns.vue index 65a364102..8d265e11e 100644 --- a/frontend/src/views/Campaigns.vue +++ b/frontend/src/views/Campaigns.vue @@ -29,7 +29,7 @@ paginated backend-pagination pagination-position="both" @page-change="onPageChange" :current-page="queryParams.page" :per-page="campaigns.perPage" :total="campaigns.total" hoverable backend-sorting @sort="onSort"> -

@@ -70,9 +70,9 @@
- -