From e932c66af07d2b6667b67c380abf02add2489f2f Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Thu, 25 Jun 2026 21:14:35 +0200 Subject: [PATCH] =?UTF-8?q?Corrections=20des=20bugs=20de=20la=20connexion?= =?UTF-8?q?=20=C3=A0=20Frisbii=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .template.env | 6 +- src/assets/salaire.png | Bin 0 -> 53900 bytes src/auth/routes.py | 20 +++++-- src/pages/_compte_shell.py | 12 ++-- src/pages/compte_abonnement.py | 92 ++++++++++++++++++++++------- src/subscriptions/client.py | 23 ++++---- src/subscriptions/plans.py | 7 ++- tests/subscriptions/test_client.py | 33 +++++++---- tests/users.test.sqlite | Bin 53248 -> 53248 bytes 9 files changed, 133 insertions(+), 60 deletions(-) create mode 100644 src/assets/salaire.png diff --git a/.template.env b/.template.env index 1c6a333..79624f9 100644 --- a/.template.env +++ b/.template.env @@ -59,7 +59,7 @@ BACKUP_ENCRYPTION_KEY= # Frisbii — gestion des abonnements (https://docs.frisbii.com) FRISBII_API_KEY= # clé PRIVÉE (priv_...), serveur uniquement -FRISBII_API_BASE_URL= # base de l'API Frisbii (déf. https://api.reepay.com, à confirmer) -FRISBII_PLAN_SIMPLE= # handle du plan "abonnement simple" (20 € HT/mois) -FRISBII_PLAN_SOUTIEN= # handle du plan "abonnement de soutien" (50 € HT/mois) +FRISBII_API_BASE_URL=https://api.frisbii.com # base de l'API Frisbii (déf. https://api.reepay.com, à confirmer) +FRISBII_PLAN_SIMPLE=abonnement-decp.info # handle du plan "abonnement simple" (20 € HT/mois) +FRISBII_PLAN_SOUTIEN=soutien-decp.info # handle du plan "abonnement de soutien" (50 € HT/mois) FRISBII_WEBHOOK_SECRET= # secret de signature des webhooks diff --git a/src/assets/salaire.png b/src/assets/salaire.png new file mode 100644 index 0000000000000000000000000000000000000000..6c742877896480f791a2c07cd17e956106840763 GIT binary patch literal 53900 zcmeFYRa9Kj7A0B)cXx*b2oebH9v}oyaCd^cyAv!41PBBP?h>5BHNoB8U4jPfUH5kX z^mzS#-pBjt8lwhPr|J~vti9G;vgeFaQIf?(BSV8gAee9Eq}3o0_;m>6nGgyRIAWzo zO9K9&I?8FgKp@b6aB$w7_jqPOr6cm?IGsUDr~O_-|1vSAT*G-(h?s$4Gxz)J|t>BBAr?a z!b_dBpao}VGi4dYGLYFQJmp=t?%5^S);&MSzdlUOu$}HEnBYmWIz%juL{t?V_aV)H zcqB>rex3gFWz36_9f}dBWgklt3#$>f6&6z79Yy@e|2}@d%w0#9{_lg^m83iBzlVQB zO8oerUx@m@2Z1I2uR&n$@H=YrKi;xK9`zHG)9$5Nle66AITiU$pDKyD!6?f!BbA;Grt@Iw#bj_Af&wHr9O?JP z-sL}?Hc{D(nGh%y0ogkV*LTeQA%)`EKX4i8=+KL#gL6q7VyJNg zU!S#}UaNHtNTF(eqqtPmndI}xt|>+o!qzZ8Urw;Hn*Kz+KFCFyWxE95vPdncPN1Mh z9PR)vQGzy1wK!af5D}tMcN{eB!kk=``Rq@nfJ16Gag^Q%PGJvCGKLrw2!z$}eIQ?( zFwL=y{x7xQv-_owNehN$Z0+QR_fCfFtyDYt;2J#d!wEu>thyDkNEI1;>wn_BYa5cX zfhsNf_Wstz5-+ZhDiFk#DQfD&Ilf`W#l>|y5MWBB;i;3F^GA_s#J)&1b3)#Sx9lE~ zG>y139?q4Ei8&8yOGa0KkfjPa?MjRAZYG*axszh_X0)*>B_~7C*zaFbMyW1ye1}9K zH27Mu>gjKKo=voHq=p}x;(s}BWHR^MO&x2nG6`)c(o~B=NLGmMy*Q)weC!gLt$WIC zh@N4sE~ zBFcp-{itkg(T1d>uafZNFIQr;fwZ0_CEd@NECzl?be+$5M)xsp4#sVzks3Fydfuj_ z5u$1m3p_shV_hKSEIn+`-s5H(jz=o`FeCWgI#1lMB|X*G>{a^O1!XLgj190{jPIeM= zD%KE5X=(1*Q9@2%-|IAo`VyUbEM@(UVKOB00N=QrB+PrqtYBz`=OcEa;`?nJSAe*CbW50@bS zykTP;7v)rAW6J!lXGn>D;+s?j%5HHS2q9^yKhM8_cY%7ESRy3d$iV?cKVI0U@af@n z#>(JpIIwrL-s}7}SCdABnkrd5pJaBSMPTU4XIUY)-UPHT$!V7#em$n z>7x2CY0$MQNbfMQ$KtVz@jIzVHk7@y=+0`cBQz|hS`4ar{JMW&tkUE3RuC8W%X>Ma z&puK`A`JFfq8yQ#h{5-IPE%n{NeePLsMu|Fn9#q)2oI0fHloWu*37=bg8}^UWZThW zXa2K3xiX{ik%jCdi%HD0#cOgoDEO^KJ?jBtIx>tljo;c-N2csj~F$>Sag zg+T6Grf#l!(0h_`HxE83p0Fc8grVHz2kuEa^Nad~DG8)edqqD{u3W-;)q|QC*swNd z)}fe;mF#WP+g}3N9|iD#KH3M{Ku)K=YC{4~0@%CXWggY)B@g6+7~}`nFQM1KD9j^o zC4|XTYSkP5!`ZiO4M9*4lQ*85f4x(r%F`P|2J`$)Rt1@0I_?|Bv?^LC1ejWm%_m3R&wcZFfg-#EP2nr;) z5JP3+n!1C^Yb!R<+cl23WFAzir}Vz+&lEf85T*+Io}qx1K%1Rz)BXk=1kaMO@%%2} zYWLU4Is9sqGz|^`VTcXJ9g|iHa%kdN6)d@GsKxv9l`Iu#12Y6!+4)1`nj6LK*cF4} zlJ<`VufGSrqtXsbMOvGgW*kwHRY-~0x-xaJ?bAr3Gil9urNz&Kme7kpKRZImSw!%y z)^7HTZWQ|rg_K*%=@}JN6w#%cj;)^B(;WTRc`>4&|J}9kzh6LVR4X5G_Mn*|kvCF{mI zzknBdADkCG%sIE`ni=92-S$uxA1V~r2b4M*VQH$PW2HzcLgJqFGY^BD;{vbiry~Kq zkS2zM#+JurYYAw40eS}1CV0uf#Q~deTn35G&WwggDYiFrWtrCVhszta7`}`)Axv}W2P6F%)H zg0{O0mS?5~rV&4vG&!G8%KL0A9fK{?ePh&-$Rzw6?TrcFFm;^v-deCthktp4vj)Z2 z4d2u0}XaU6-^-WU9G%yaA9@8;us+)?#*hnWs$1q!=I|1WZZ z7n|bmgdo715NVt96d6e-`$-5x+ZI!qJux9P%-xi#w8tr8FVIHreTeI*3khnllZ?dp zn$JCnf^99bmA4KqT$k$jRej%PwM+ye8!m+>@LU->Q0j;n+`D2+KIp%8Z)j_nGnwQt z%w<5Lx%>Q(MF*1N7&Ya){tQIA;Lx8ZT5oe!>&gHUvwAw_ZLPdNIjpJqW3JZh8xXFK zLQGD25JLQv2$#2hvGN$FBvO1JG&(!}vdLcD%?}y8IkZ@@BaLBrC#EZtb}+9b0-@<% z!K@JB{_`p{nk@w{?5={!yitF_$e$wJKx?n1e9X6{ap-<=i}R&{mr*v2ZK{H}4fNlJ zM>H^!p*=Z!16Zh5D-wHcCX6)`;~+BDM!vF{YXyou(eu27;2l zbDeKqR+InOW%wq4x*7Wt#i?>Xi*ceaV!i6%{h?$7TbSObzH(*UA9Sa>?%%UUx>Y+R zN-@Tc6n$7&Pws$+ur@N5_02%^E4o%regw*mU2htZQf<9JS>uP75a#?1%lhjB&ur?G zwIpwLjawT|`pufpKgGKO8Gf@_fXlD(oo1R+P+&_d@IhcN-ah(K+^B@9Pp9SkZdcnqj4V zOc~!+PHHHYMu${Rcdg6%7ZN)x#W7l{27WF)0HBp#2rTK#k;?vp!9S#?PMtN^>fYhu zQktYwCcS;ZuQwYQA0QmF%}dD;i`6+rj9Fb1uZ$KLtv0>!>u&$nz4>X0mOz7>URWhW zh@H}ylt7i)tij@nqxrPREh<16R&|{|$)J~)&FjQu))PoEe=&O8lp9Pj!gR#GU$L?s zDU-H=Ql&wI-8=%775_#^zN!s9iux0jbO+adtG~Va>YLnYcdRMx(e17HOn*WqA2B@S z)gVe=80mHp25}w+A3H^ym1Ucq(9xcWyE9x4#wruw z-X@xqDBxP3elx8CwbJ2sMpj`hE-!-5e6&s9TBs@L{N>x0`wLObj=6IPy;QaolRA_} z)JHAUNaLo3Fyd4!%_ZfUqNqdjorT0kF{H_&#RqMj`oU4_)W6Pc_w2SSfwewON9e4b zMVD2!?r5aqjdixzI{i>s8OT7A@QdDh8IO}tp)WIpt$*WYHp~>sm%UjB#zoEbG)ECG^eJuJ313I0BOIY$5eBB{FgK?GODR$Uj z60AnsGb??dwg=q6-R)?LDq%GulIDPB;0pemr~Ajct*i28W&nm?#(7k@ylmwvLhuSU zN@@{3pP*7!2RP+5j!E2OMq%{YrjnNz~<7m8J1L`snt%Sy+ zXFVwrptODjfA`l0k-Do(F^Z;bxx^7({FccW=l-gGBX|K)=^J~x%d&9&x4vBa*q&5B zp3l8OOwYF1%VAL;tEJ=q(&$bwr$jt6#SV`5`v*ELeNHWFGQrdBWaMEj6l6#MyRm*$ z#&c=!xv|&{&lcS4D*dJ<>K;a21fRYK zVSR*w2g)iRcFIX<98KK%CC*B(leZlTw!MWk&1XGu?`Tde$Y(>|z#}|0Gb%bRd^xtG z2zgt=g>mEKQ|aZwCPW?icV9#JY#OWM&P2DWjqic<2Q6%+bJk*RNb{#i5&G2LJ`XIg zScPj}vd9#mK)APGf7r23b}?n`b*B~y-#t%|3$%1l?!o7aJK_q(Mru^B)^--fu~uKH z{ya?gYTObU(Z=}T#ZI0YM)+VyT@UL2EBI;oMPoR76zoYF`Z>XaB>=fUq^3#j*~bR$ z11@%41c>LTzRemcw?Ogx22xphNRFFb5%LRkqIWOz>(z;1*bwkxXU8ygC&lzdED5e@ znJX3ke%w+7B{dtbbqf1&t8MPq)ld9OPrEsPC(y*cn`cR6iGGDcHptN*Yp~?W@NxRk=fgVTIRiAvf zm-W^e_ZQTA15f#&?PqjtV4dhLyVux=(v@~2+Gd_?{8#H-)X7ynqdxs|xuHN(C06e~ z)qF=iC$HtYBDQ~i+45>c-EByxLq4XJg3VgozshS$5_F>(O&=*_dN2JsL&}s?|*>dkqy^XWg(R|ot3jb%%Bzx(Y&pRwk1+(q%u>DNy+?HT(yDVF57 zXTzF}g}bI7ujif%ez*~x|@F#!~LW1=SL%nMR#%2&d5NxFh@wCISvdresP`h zeB$2qU)Fm*%O~jIoJdpXihkzNmB(d*I8%4XHIhZ7&W+wVxa%nfHF@u}mO*0d((CD$ zKoq9mQ+GJrPn4d+`b}sQI@s9kNI|DKF0^+hou9<1)ZN!LH#;`(JgEEY@*#;><5p?)z4-WVfG zt#uEY!9DY7^lUn{A%hSpL=i0#wIx&Pz=K+`X0Z;l4|7OoGx9dtaL0ySsZv)YsC?wl z)dd{Hig2hmdgT4}rE3-IoG39tl2G}lm4r|xc7;rw@IoUG)*oaq?2jj+oQ=oCffeZ1 zV{Wv6f@D)tM`U{S{Jb`TJ8Ney;1w<<{)Wh7f)*1I6Jt)18w1Nwn8cW z_sAy34-^jCDUo?A+Y-#*j4X6W%DRgqA-6pssnqu!yP=lEc_onkpFte)iazKP+sSrG`NXL)A zWnhzM9tztB&5q-A8O`2M?-Ju{pRbOUKl#99Dxif;-M7(Do<;Wv?F6&|xVRxV+8dYb zSTwKa7k+K(6n$R%`tfx;x07-;$j^^w5^*$+_YUf!`FY_wRP^n#u>}sKmM-iioqsJ@ zs|6~Wnwtn^qsdAw<=;JTI9Kbdu8t#6=g!f$sl*`gv`)M^INE@NT(>lk`o!@YSUVgz z8jJMKQM`9k)*QXtjp({Ljp*|HckbtTQdE?*^*bm0-+JC17wdyv*`iuZOLn|hLnYOB z(^AA13*}Gkhi`EKo*-SfHjvtQB%%#78}jpzp%hpGVpTzYJMd+xq=zL@|o@!4bot25d0$2 zVa@|jux0Gmd?%QZwEZyQ5FtP0*9zONQ)+--1%&loEizNE755QxZDjotpetv$aZmch z{vXRH=VyxOEVwo7geeAB$M~p$d5(|atQp_L>qi*J=8a zirKT)l=Rx!OikmJy5Ougsn4K~>=Ch>i>YVcyCQ(mX?0o;p)UCP(?14f-^LZC>js{) z#?2*HZ~BY}<+>$-jTY_3Jdz6b1@_qhRtt{Q)7xJUT8&d@2FGzq@O8(;MD-;O<0dDn zeIcSy?3vXnzPg;E+UttUt}sl`zekiw3jlzc&(%jlr~#7u`7359x_ZO6;-yb4r#0nc z-TynH^mOQ<1uYP+lS}-((8Z0r{*xFMu+SJaDj3qzfwMeUzp?!WO))w{D4oDC@iG;& z+zWC21fmFZ@=DqQ3i4kA-WVKHmy`{c)Z`pKY6uoF*iw28$csyfR`y5I5+CuWk6C;A z*?O=}0!}?Ck3Anf7C9XFd%N#CmlUIVlavleDtewYw&-mXILcf24Se~cu><&Hzz2hw zcwvdaD`oF~kOi~HI6G;);gw*7j@@3WFZ@TYJhtR88)ra4&`@NxFoAd+SV(G}szV*E^ zlH}cZ8beLFJiJL>SEB%bHvc)b#R_#qslBLrcNm;mYw!Q;wyXw{7RF zQIKWPfOO-`0NWq9y4PgEeK2kT!;Y^Tr6HKf!hcJaiUYaWSYD8ftfX5sF0I(%APkaF zf3k$u#b3Os;LEO4jTT>dEPEzkFdey&xe^-ZPhrV7yI3qL6@5*2W=JEU>|&6p<@e9V`-H~E_U9AN8WRxwahey(2+UppVDUaee>dl|v z7|cutMHnu)%SiI$rmw8iHJ@GkFo0&dK~Q#sxu+7v0VDdf_XQW;>GUrO-|D(2-H*4< zJ6kW4aF&ye#4wYDIS8waJ0~2t%hjGs|D&PpM@}AAXm>fW>*=MqlgwQ@ZtjwIHD-6u3aRXwJsXN}a1VoamKT`q z85qfu2}0Xe62kVIco!e;$!cnxrsf*sBS%USxAIA0(Z0CnF3`&e&d`f5LE< zt!!gqh{x;*{W87oZR3&DVa1tVwZlhn>&>n-BP&J~r1lczbDw2rP8@Z&BhTHbGWcR* zap_6wjz_ZQ$63VF!+Ev$Dr)g7F7hhc6QD;Vgjw9imY=}+B~^pFur=BInNQsY89n@K zW>E-w{^f~fE|?m!#4E^pe*TA$1WbMZs|b?!ZRiwC`4T5lfKl3kQ>+{hcj}Ln$ruDZ zFqgBuQm6@av@3uUb6{@&(h1u=$i=|!j;Ub+uY{eY$e68lDp_Xq^e#SlHbPg+7%jDt z_%M+0-M5oAtW+s==3O1#zI3hh5!zeltrqG#w+>brCk1_38x#F4%2b*lwL^t;#${Xh zG|E6`Yk6yPmGmacy)VhlmF#pY)wEsv^nREJz&18xf9lx{rfJsa>o&Tk*&x$e&-Ecn ztHT@;D%`Q6hJ(KZ?@*p@?%;16d;Fo#DiZp_HvZk^Lm>8Tu7e+uonSLEd-(`y6H$ig zZ3L}|ZKb&C(9;&#V$gpcj7=MhEMK;XQFhxU$93?y$cn#8Sg-5Jae6{Ou%&_ICk4d_JnG zrdUlcyFyrHP2C5`$dSPKA5v*+ogJ$Q$GabwXs2Dyl}0_PYHkf1{x7G$mnEo zj}-e;FOENvkeXIRTE;)b_?D&&pUV+^u$P+cBL7B_e7+WmBu0iHk$b(3<-F=aJgcBQ#p!GOJVMIFI)XkPI5 zDiLEp*=y$IX1hN#??88!5pQs$Xq!U(c(VRHiv_2?=2~QQ_h`-JUw}lBU!cs4Q)S<$ z1Kh)cAO(gXXd0SZ>~zp3DzCBsP+bOkXW4>5xvOQIl+u+w+CV!yoFy*rZK9yZs zOiJz7Mx8uF_UhQ8Ab%=7g+I2iE&Ela1##N;e&}Btf{#1Yh$gBZmM8AjgZ_$ z{`Bk`d33%!MB3yr#f7ntR&WwuJ`7`PitR=ON|O2DS~-)Qm$MFEc@vEAT;>jTiEMW1 z{I1zVVh*gkPVag;_s%?TZc!nD(Z_oBnx%?IbbVXZ;imG|2%RCqG|6vO^=HBln-gxF z)u}!qHTH|9iy}FJes$s}*C`V}onV1C$A(v#*^p8cEM-jdb~FLUrh%4JX%l3 zus|2I{tD|M<9#qNKu~#~=TRH9Tc3mXHO_1O4zKeJHImrQy$1;Ox$|v*(=xJ<-NM7I z_828(TsCZVZ-EgGqNhLjZWV6zERE~*6mxn*9LnbJ_@*N07vIY+c&CYM)ufgO$m(9X zbMW&2PHuVNOnh;vm(y_u_GOwAY)M?C&JxFju$)>;QxZjrIfxYvcaucL4!>j zIPN&GJ_ZT=mKIOXSn>3&C}2S~v@yZ-iy$2d$ee*XFZ%vYMW|}bVK`tDo8^bDZNHO| zVE&TTS_FUM->7?!qXJEN;&vcDVkhPzQ{0^-&HC^=OH~)%&5!`?I}M44%fZ^~TV|D+ zw4x1qm6gdLP0@ugKiOiMFDb~+$}=-DYUSf1%HMT$1nUqaY9*hsY^uFOaj+d*Um3VI zT4?12UCZ;H+qKpwF5+b9Q|bpZ>e5}OsrJDbD^QpjKMb2p^48?!IvZ7Ypyy+D6|?o7 zP4qtF=60ibE*^5Q9ozkzKcG9*Yprc$Bm^XO-K(Uv1D*(&$KeEEmZ$UK{pq^@La-6- z^*&-vckHz{-z$Z>(9>9f7g}|k*yid(r`PU1pw@6a+tgB^B93Ep_|sl}Vo$VK&lk|> zf(M4nV*t2?20TnP?GZ1K=zE_dtPc;oUp>g{&2&hQ>|(U`@g4g8@4vLG-5=vE_u97L zn+A`rJ1-+*BOFp7;P!hh*p_vY=itfaF!ZbUP+e zx_CMslyTR)uh+Ge;SmH1Y3Ri--di`Hbu0Wf!&|JRAysax9UB+*(rYl+xSsRiLyxmq zsH?yr1fhNS&+GF*Zps&;FlB#!qmT8qPgaCf-v9V&JbujEJ`%H>pH34`2Yv1QD|0wp z(*28gy9-1>tAVxcta?m=ZHAAr7ri@md3Q4VD^MGgX}>fMrlBG|3_93;0T0oz6Nn37 zt$p>a0&E9~iOq(mhP&&D1w4Nx7vIUAhE`ckEjtm0)L?4%LkwcHH{(-flJ1t*UYxqY zub~zn|K$G~)IP6L2|}*hmMmQy1QWD)(!s1Qa@Y$D3PJffHotsl>#PjEVXAF?-~04y zY?kwM-5hKs)*Zfl1LAZiPmAKPpjiZ{VBoStjY`+Z>g8u4$VcCB$n35v<)*^(Qx*mA-ER&L>kFjy+-9d3`;Eidrp42%vOZD^fKXPvMAp zht|oK9McP6q~XNSM%nA>vPk|NRtNA@6iAZkIAj%+Q?o~wNPz>?kH?Ae{hU8{K0d5e zzvEz`Cqru_0CfVtZ>O)Xh0`1SPj^hr)AJToR(nTg*iN|3LF42UV%PiL2g~>OCVk)b zZO8}JPxaM1w;oAzq~IWP$SmFzg=6F~T5Z)Rn!`$+yP@K8FYqE{FXxzxt)$}zJ{@-*| zu=$+dW*Jq~e#@nNQDR5mg!iBhpGTVYdEIlV8uz-w=CQv4hU~Rjn2=|_dR0dA9Jz5Q zGM=A>7WEQ8{~5lYY#XyE&F;G{fl6GsLhIEi*bn?PZau&Ms~u}#Uj3k+>7mpVTj7YY+QhUr6Nuz^r3Q%M5YIMHT&&r z;2O`m=$gM6+P>1T;7-lrBcV-xOM5WSvYdAOusIJmSiEqI8*tIZ{_{8 zQn-<^(hHSeB7#HiUj8OE>>15G@L+ZbOP`q)SF`B>4`(avGJ2u)tMhZiMwJ?k9(!a) zyHRQLO@cogYo9tAE1Pix?yiYgRc-qUJp({TBxxbfWYX1AW#uhAo*?D8u%dyw>>*g~ z{qzbpVW$%eXfk7a&Cbov0; zqXkba$jwZ|!r8`2AI@PxRgLD<9P!{U^98CyhZbtck5ap^0w`J#7IWvqzW?xU(@0UJ zJ{1-9UI=g$l=%oX>CX{cPdxfQ#c)Pz7oH%VK76QO1szG}XqUl}i>kIdSGH6O4&nMs zd%Rt$i;pw=W0PG5f0x0|;%`w;yg6j$^%jRK6Ac;ej3z(2;0K-tZza6tx*{ZHOO2HS z0~n-64u76?WN*IOMf5xxLkAU}r&D!cJ=E>VCBOn7+4(}#p@#QenFx2>wXVmxYkkIs zosSrVf7&g-PUnTJtJO%5oo7wO))S+AS&%o)6;&0LCa;$sf36a1o2XMvqoa1gFb%(~ zFx@Q0=sT7)AB?q-%M1Zs51z{!RobNU^&T@$REBtTWQgsGCWF2EZhlzK>AWfNYE&9m zw<08PYC(ynA`5Ak%<*S+#74z4*b-z{w@3X#N>SK0Z5o%2SWjoazR-FrXqWi!<~4+W zmQd8Y+SZ&ZscaD6u+C*@5w~IE7eUs6@4@j#eG_3KZ@U#Yn4i;_eqdTQsDt+H@PCB0 z4}G>tdGPJS@LLW11HM-`ZxT#SkN=7UU)eAD%)|ZoyL?>S#A~*bE^V!b149Zc8R6C{ zf$LjW^BA&V9&82Ek|bYM__jH?D|2*l~{a{MXgAJ0Q1N!&iyZa2g^6--))wQ8M%k|(3rkKdfE^tEg#i;9|OWwao zfeW?02V1x7Qe4AQPV8IOA5`&{Cxg-;h5;uf67#$>WJa-lC|U*{GHuI#cnul&wDK3Q z(Lz8QHh?|S-u%)D1=BGi8Hx^I(df^^d$vn-{{`*mtwQM%{~-edH<>4Y8t4sJw*Z5) zcJ=B6&@Z6a)DvenwPM;^Blf;PG`6-z0Q3Pc;yS;b9;aZjksK(&@(?h*npzj z<7Dbfr(h^j!$yUNc~qJa_xH-pkR$gHlr~#t@!nk=g}1Td0LPZVwwmeGXZbw2PiKUO zTw%WE!v%*?1fM_Lz>fCEV#MA31*9tP9Gs*F?eLv%vx2@FH>PG}L*As$4Z?~dU z`KB2@m8TQsqtl2P!fg}n>v`fRUY|xNpr3%dMHaLDP0R-e@t^m^fQ=aZ{W$?t6Dh^_ zb?$YxoC&tzvimi|`(Wu_du0}5w=KjcT`4U(X`_L@_N$5S7Xo_UW$+!mx9padLZ1!w zA{bY0C}##KgDy64ZoHt`Wl|BKsP(-yE$=FW2lHp;mRelcAs* zCFo9BTAdj@hjD6AiN8@0TlSdzZE)-k^MOrip&AkE2}853Z5S)%BJ&k4$;ibOBI|Wv zCNh{#5j|M77Kwfx3@t&UO!rRItf#p%LTuIdARv6;FA;v-OB|iwM{n*)0~^VbDHfJH zNP$Yiw{{yvUI{J!Qc(YKxm=6ikEAyqL!z*)_wW2Pcg=>DlL%OHeaA-+=Dq)~XZ%0@%>TE8U-W!~z^=WX1)kw< zkTmRdB*N{T&^M$wuK(xQ~#>`P?y;Efz6CP!gsckCoGET{w>fSO{ov0LgVTkzy;$BTVX`bl*n6ji~a z+2OjrI^+1G*F*U~ugG8w`a#WRigcg=b+3VjhJq$0)vu669+ z8#<{1U9`NdNzpfPD7pVmin3fE7W=x#2P}{l`Huu&($Ho`XT$@b}Y+&i{x% z*rPDK6*)2XwSL#+eLm>_IY|{VIZAi@7MK8w#GH&|YZ(}7tf=3}xK84~XNV*W5_->g z?&7lB!@SE`@E9iM_|Hqa|3Vaa&1mu;h68xz{C^L^Z1exKLH?(!I6M#Mg)Q`6Kkd|v zl(iME2aMH(9K(qT;);ZT7Ob_=*TzrK(U<>q%j7>0FB>WXN?9NSk^uS!2}0zYL3BJP zLvR)z2OJ1onu!^ya<-uCBM1l-5CU*`-y4K5Au!BHPk!Dc$fSGSjvyeo*b5X%fp6)M zNjgB#+zv6FhpKMFwQv%sN#PUB(-o5OWlgXlGK9r92wmb|!$QbQHoGLG*h>gQ4coOC z-}v)g`*T(Ihj~+&U+s&bLS8E1y})p$K0i?}LjEwcKsz=SCG=j*Nd_P$E8vKIHEDkB zFAah1#IC%1V+=ulWIY{*AB`=>UGeP`>>KP7EUx%iD zvu}Hj+C?V}2l;Lq{Mx~ppjAf~=%IF)06`U7pNouGF$Rb4Ql9zC>Q}HID=LaG63~FW zaFt2m;UowIX`kY9wnf%{paT1o(czKgy&5f|TN_V>4-rh7nnVVQMk|!u=QnFn-`(i<&j2eKspgoqK%t?0CLc_0W<-A2qA3^7*$1_@6j zkogY0mmT=@D6}_PNQE8D^^mkpYWNIp-XrNH`kj3H&cO*`U&;)ECfiCvHQ4P%ZG0~t zjJw0VX#nR0Fw^gAs#F%kyuW;1jUeB_u3s!Kv=d?p_Vmm`IuZVY$Q~=d$ZZ5#R1ZDD zXK>5b%}us|^8OJh^L7d0^ryyvr&uw4e-3*GLr>(88}U_2e$YU3bvurmMhbQd)K*H2?rE}*~vbIkrs~h3w)daY^VefOk zyk^ERh9hez+Oo2rT9Z?S2eg71aEZL}vjGKv8(P#&+uT&?%Ax!nQ4@<1qY2FLh93*AR&Op!c_^@dc@5b}c-Eba_Lup5qcK-`qLu{sFBue(D;^Q+>`%)tP(uAki~DNb zfCLmq18cP7(4v+F`nWyaURstHv(A|M)ME4kIHZ4qz@5igK2;wi#>u{mJMRc1T}y() zf`E2T+*S(@rjrB&ZiEoZy2VMf`^ziT-xT;}jTU!{BaHQIN(6>@Rwvx69h&uPX0be> z9tWfQXX^z%IQu7`s=6L}hKK;^iVoOfE@C(~0S-iK z(NBfNUmUnF?M*9XI#P|zWm3RWbvv#EnXrdPDh(@n3`31gSchx>EY70Qdq^p})MbN7 z9W8}Eb#ZyaXFweRCUyi6BThK#Cr@ct zb{}Gk(5U~?sw@GvtdJX0hy#29vYdg*DP~fb^2dffu5X#1N2@xA0O%^l=F9O{2qBJp zF!|ee+-lA+emA$2$GsdyfNUYy_dZwsz; zQH^!?fOWkCNw!%y5d!(9_KDG6LBc8!X}9=M^hpkLKdpxUxz5&_oQyNhbG&mKdDv7` z1C-_=rKL@T*DW=q0gXI_PHWTE-}CTLd$F$jX4f8JQkMHuA6n7JJrte!(4hO#C~xk1 zHywL+puts2_i{L|dtj^rind-XzRb5r$Hn4>ZRGM4hf>7kfX!A??$F2ev>c|V`m_As z`_iCUvu9KRHc<0F{verwV1}(wk|@_iXO-)lj4k&7z*-3@SgAdh9TE6|nI{>J&ea)I z{;>DHFMKt6DvrhHSVS!(!1uFheb1`zR|_tZ@r2UXw4P6UMJRurz${N7(Zi(_hc4@X zaRF5O;jG4fa%H`@3{)F)S>sjqco8n^wJ{piDkTKk2f!Q);UB|cVs+>(d%B@KXOvz0 zq+Nwl_3|RDyk%T-@Ke`d4afbz^z`7-Cg&2z{7JwXr8o7<()dIBO`awhb-E80WU?_ZGTe6PtpP!*}cQa29Cx2DOYRyD=7jtHCmf0p(VqpzZq+BS- z%8|Q3S_6IL*RbV}Q*~-h`A!uFq`#|0Rl^*&K!ccf$GBd%!<%)J>vp9LKw>|7ox@V? z`7>W{Y^ASolhqUeNwx}q>j3qj?f$s2Rs$n%OWSB&G^N9Jhrmixt~`IqO3VEcuI$Kz z@5WN?p)XB;iSAjqt`P9uKh8sSYW@zv_ugW3vEX`k<6&h&?XK$$55HK3;F~oa z5xIDu@~;CF#MK8w?@x}q&werd&c!2%VOaFqLs3xXA+|q=mhrP@VSxe9uw}qun_Lvq z9H86=e_zBD*ea|oiYc2QKRe&11GLhOCC1Vnr2W#%bS)R0Zte9@797;6VQ~L;>^-Hy-<%IekY$YX7 zmRX#t;F?j|3oKOBy|4@3gpX}v7S3GP`HGU9(w<@?UFVJ?jn|ytF@ml$Tv}yUOnNg< z$R^4;8@X-|T8BRqi3n-tIPm%D_n-L*QIpkr-qzH}-{<8QF9)gw?8p`=rD|_n{yplHe1ciBaP@Ke%LhK<*>Zr4>`C_#hKGG_8%b;C~+318l-9g9d z2?HmmMdDxnbl}}u=SzNpGL@TgjL+{~W0^T3om{uQ=CKe;RnMh8vH+MFE`DlOkk{W0a}( zIQ#XBr|loPKk9ZLijQSA?+#i;su;Mi-^+xW435&YQ=P(evXT)GJ-T_lcl07JP!_O~ zvgMMUnpxl>2F#J8~@Kv)cT}rc5D6X5|sGn zTKpF~sk!<`lRR~+z&$&EPJEMGSjeQ7Fd&}rVkz!9C=2n0W)LkE12f^B6mn>&O3`B?b=QBtAAbY`86Q~E4{os zC}Ageh&Xi2te5z}Id>?%xOieC%ENu_LA~#651TP>DcyQH?ex-n=ZOLqU+++5fw4P= zyUcHn-nSXa>yR)MgwZ*D#_|{BD@+7!Cg~U54-x4R1E*bezToO3%ggMnI>vYSHhLS+ zQbYwQjDs>bQd#-a4MW&xGJ`s-ItH%e@rS9u?5-F~@E1KcIVkU9S_q$yhyb_2K4-v` zv~Cu=cOxTF)JRn4F}@yiE?xc+AsKPGDRMS9KXG%>_r788mvnCTKgqJhoWg9}0}o>3 zo{>2Y5+B3Y58ne0J8G-fIXv!Zq!D~NO%T!g$tsOKa5e1udz1%u-m>F*nf+5*tYRX4 zFB{&m0#qH|_|C6JYmNvHoE^7fv~H}RDPGT{TJA4 zLY%&iINQpxeW%?tH+zd9;8j$YVp?S1oo=x+elZ-c$99Rp#_BvoeN93}9Z6$|4TzzRi_hxgJ)~CFZ!-@r4zkJ&!g{mPa)%}n0jA*U2Tz~8?99Spv)4^wJ+hW9 zN))2(EZM)%y6TNX$LoyRT_+;T>M=Y{3+@4Qt56G1*DNKN$GB+J4Cvqk?!tBTDsYQ(k0zzZGGNz-ZRGe`JF%CIE>*K+wGQp z-|Jp;&TC%tnsZI)-&ebM{MpC$I64y<=eg?``{@-Q7nFj+BE(Kkjj*tjS*=LVaTGcJ zl&6qGsTM~Zj_nfn0pV^rwqyg1@EmH6VrWW1n2y$xzgfOC+zP9 z4_s~|kBL%9X!m{V@)4HPNzhQvdQT3m6P#jEBV>>}G7i(iLknpaPda&l7UzLA2!Rv9 zHcPPbm!HH>+N_hGD*=mw8umEmHILg`7!~HHUj9-UzUZ-mWHTzUqJH z4{!De67ca%y^fkt;upHj%nLRrbt2ka381utP6vE1uwY+3$&i<}o{$q$LIv{XqUr{v zE<{9q+rWL3-=hm7(oM&whjb`Z&CP|#LZ1di6kG+G1vnDp1(CiZe;#u+JKE_Yu0M|9 zDEMyuaPp^-XFwE*t2*)cE}krPsK{TDN=ieK)i^vNP?4&yt1ePvpaEfzNAX!pz=TKh z;?enf6*loFO*bP>&59doHsE1I#bU83tYn^CyS7hgFPWhe*hRddruIjqe#7R~&pUsj z!OLC_DBH7`dkk6eJ7|V3xtNuL07Z-A6J?prmsxK?Wau^=bk3X(*^U<%7dUGw3Pc4m zhDfdEqkD|RAxllI(s>&cP~3o4ELid>F53RG@o1uNTA1Tul6>tomqcfIF!{_KiA7*0 zSoXtJR%>;R-aDBv2xJ3qdNsZ6HXI>n|E1c!hZfQH`T3;YK$ddz{We7qA4CI_Bc8Ex zzxrnm*V@76OlaPG*|G}>sZ&PJ$%SmdJA_5&t{029-Od=!TSTklI=h$0lYE-* z6~~)+nI_x3=G_pwaQ$FAo;n{<@{u~PP;s)HxI>6$Qx&96un9S+iZ6q9F3GqB+1lHS z=%S@Ljw&_yIzS`0{bMW`%!NNC>v9Zdi)4M~X^i1SQMqpeh31)a3EXif97;*lSgOS(t!HJvpRMJ!QIHxJt3Re5-ASR%!i3Oj%nX-Z$-Tti zB1p8ZMpTpTyl@RPJm zsi^vB{U|)|oT_<>*&PefN6%YcVdd(2_?H+25-}`UJeSI}-~R|&95LM z_?j?34;MRTwW-A9cxk%IMu|R}Y&Q%-$#v;swpyz2t+5q;ss>>reloM5S;37STcK@$ z*H{zN{Ybg*QJqRymsodX==U>K@p8hv@W>|+kA$QM$pO>4X|V{1uBO5ut~Q>&pQllk zik5%W*6l13B}5qPX<&k)O<%VtVrnhi%-;vs9o=N}cydi$4XP1RKX1n^^KL{;sr+Mm za+^sc#7Oqpi^9~Lb8T$f?)EUqklUkC)a|=)iJ~5~`pG-W*GLNbye^GJ@3r&$GL`Vv zuTE^qc301I1bHx`R`fNKS$9Y9)mb?ng}uVTBG?M3;j?RE;^gu+=cj0}B-02tHd;I; z1`a^)I9&XB&n2v8_oq9wjV<$S9IHIy%&0Jn$t*6?|jGUOXGbr@`41l+jX}QgCK~ zQfYGB56EgkvKI*)8Z03E$cTh~qEws9Kk0)NHTJh6jQ51*?uB^8#Y>$o9VCJl{2iau zHZy&t;G1w3>awb5gttpV1DHs+RDgb;Afh!vWj-(6Ij^{KU&Pp_EU18`f7B_XvapoW ziTC{N;dq>FSLe}W8t$W;(>v9uWkHHTEAm~NykQm0lATxYooJZoiRzdr5YkkstGAok z6Zv1^;7&X{0`UX7AiHlu0KzVG>LW1{W@U-CHU8G_XAtmMJ%^q)pAS%YR6)NMtc+7y zlZSm+Hw5@Zp$O0Aa=JpDTR>!zf?^|pl=*GP>?AAj+Zz1tGdWIizq3Is(TNT{&D5{o zyvkS@UXC6#s=v98sRz?Vp0$?Mkln}Ur00|wX-PgmpNzk}d$9F)?xW-3QxhcZ9iQS{Z0%72EF z=qPIR!mzL78IdeOPB;AO{mN|Upuo|>ct5Yk&+X#RV4Z+h^N(mcQTQ+s?pvAeNkAYc z-!8u}vf4C6HaWSCO_0+wd=g|}>haWo+T$n#riAPxU%^!J@74_Y@|y5iEQKaaF#s#Qp$?L9=Yl5j>kR4itUc~Eg5mO4?svraA$^BLE7|7v zql+BaP!SzdfCw)}FZ4%aa1w_I!imVjn@VN<+M@W$y>)8-(@nv#CrfG9J?_UZRipBrhN$uP+jI(#>i2HV$#}Fs*HNUv z2Xxj>Eh<@?pxh}*N}yynj8epK}SEW`6#F1!cP^U~RG1T(A#WR=+CW%H+} zBK5{jI1s(15&rzg;jb6jWf;@-Q?fjsH)T!KOtz5oI5ii~Tx|VvykoMr76HOh%v46g zJZX8EFf0LS0J9_Vmq)t6ni!O|F#BXTuW5W$oj)KZewH2xT*ZUY8DLY|H!T(pMarz% z^!qw$4SX_+d7kYcLRvcfJ^L&%*`GZxJNz?X`~HOyE5IVZy{p7MO9ZcfN0&RmyjSiL zxn1h8O=;d;WiWUW%@0$LZZB@K`{|2goF?GAvH$8euBS+F2z}@h=BEV+<7U>Uybt5k ze_EX6_jWBzaQh6ICmdSt|W%^gbq+?Ugw~7HyOWoW(4_aZ_7M=M?YFhigvX-nyOUwK1+ge}ONCu1K zM-U25jNn}uBIFioOgzOH#>vg?(jCs=u2wX?0i~7o-|%8= zmZz{Bjq2GfDr!ER@QOial;-HFUl~SL?PvEM%;#;T@@z1h(Ej6~16tAgl92wK_zZmN z09$*leW3J$=6d(p59VK%MH^+Wu^wcIZ`tBCU$+=EOH^6rtfn7cqJl8i&hN4wcC@hyZ!UF`^(W2 zETMk2NzSpPVM~>Rr9Dg{-u3NePQJ|6@vGHTHLWnAJ3~?(#r^K`Zs8o706j$w?zLy0 zfD^%9SL`Z3l@|(WJ#^4_e5n6RSeW|QI4iALd-n?~7jQAEzP$gl?DR=U{kgz?bGR(J zn6JQI$(%#uLOGDfJs*2VZ_)QUc6JMt@p%gjMg`0gV z?s;eV7YW&)b4)?XUr=L%AWwsj?y{3MbcP+Z)cMCih|t*xE@isn)>m3n-Bzqf1-wKmG|_&T#oO*OGh3fLSUgg^Rct8 zIXOC4O39@`nhVQTBv^I@^A%m7Xlfgj`R$i&Sw;@A+u!}H`ElW5(rM-eHtw>k-rGv= zJ4m$VY}587qFk+|8s|sl=NsBBTY{YW{v|da!p0frr99pjFnuG8pXyqC(@~xNX@)bp zT2+aXkMZ3si;hfYwEm9*X-K;q9VPQpttrv6ixDkFlV@`^Dx};=iaBzw=&tGdGpgf1 z3OE>nAJ9el2dTm1Vxo^AhhD9eE1ygkUN@A1#vpwM86=3Fl4@&tnzFOTjQWjBgZ|JM zs~HzWI9nA-q^M9eYVWod3kA|dK5aIdM)Y#Wp4~#L`sil4bf3A*Tr}P@tQ;5>V|vUB zfnUwxlmr;MhvxxMoa{SR6W@J{!9~rdh11KtJwj~rWnGW$W_05j{T&;+G;VbLtT|JC zr_590y+<9XbC)1KDVv!1#_v25=FaSbRbLUa^3ps>kd2f`^<-a)(PGAJ#f)H_=lli} z*MN}VbFn(S-A{xI54k+NkUCyDjFOSx#%*ix2tK;O7b3?!bNqF~WZAL(IprsGeM-=O z*>g#n3a@0zD{pC9dEuNevv#?b`EuvO8zLOSr#nHOqo*Ne*btvsSCvhbZwKqv7|3PN zWl~A~Jh65mU07;;xZRM*#)$%69$nb>;VZ3y5l64Tz39Lixj4?D0aURpT3$!eem+4nEv!b((4U*GyfFqUb}GoAnr6r^3&BT z3m}hGE<{RLe*Bj5Aez&;u&|ams<{H2Wl;$KQR{)rMy>5nB?v^fOlXQ_kh|dG zU;mG(aFg9{J?I4+RPgeoMM?ZF&DEqw(BnM&BzvA~NR}XvzwC0mzPd@1#k(3;Py@Xe z1J$o>guIUVU0k|vp@u+wOKs+asMg9yV_4Ev$0!{jL*JJbF3wmfT2 zbzE*aE3?MP%xr*fYokRsk9$mCaW5TWsp*GtlR+2jSmEr1D7UEwJ+9+iuTw+osGnqg za_RQ%jdgd-1pcMbFg96DnWmFbg~sv5LK0}2pip}kBK0Z9vhZwf4Ja7S&{QqC16-BX z{MTGtm#x|g8CF4ZQ|3{P3JdXT;o@krNdABonsOY5k&aWOa{7IlSrjd!5St?-9xIPi zO|922k#QycsTs#mtw`X_chI=MXn&qckuwDFcKf5l=K&mM-Q_N84q$xk8z1iJNjwPO zri5&%9o#sVewHhW;dCyd6Vb|_S}mF(?2;`Jeh9v$^nHEg2N7-n53XSh-acYl z0s4JcjjLOlO&_#ymXIyI{QDPH^UZ(8X}!fFZPo@+Zo#a}qF6%e_f z{p}4A7vB(tX%zqdFn9lJ82SJ7&77~CpssS4{2>=TE&2^9Ao-=c=e(7^Z~nakLi8|E z)P{z@2S;H4cE~R`ev_!e5fA1-wTvWd>P-6S#{x_??@KI8=&-cM1WC_w%C4Xa&2k*u@DR99|V=GI4T3pzw^z)(?V*9gwK+R_f8p8h4 z(12hp>t7w)2y5FFCqysmgp;fjFF-S)s@vj`04m1^HplVI1{#NK+x%v9RwJ3 z)K-3%P9NRzBp1qPgtiV_(IJ#13F{kY0@_4=r5w6w+`r$u_Dtzp&uerUC3xjP{(h$6 z)I693l8!N2Gdc6shKO0AaF_|nQzdEnXLSE=rLXXhUxx)5UOaC>N|cy>q3_VV0p}rQ`d+zp_BZ%VSt*D~jhOYVPeQ{1co0PEJt9x{1!ibW=?ikKN=(4rPxwq}+kFRxVRDC*z)X*NPhtp3j0)F-0< z!vCi_S`8JcoxR`bJFuWTJ`$M$65FU%v^Y%3$BaYMQvcS}M8&0jBL)c^-!v?l*GZ9DI%XpGC=Wo|c-^A9#%E1b(!DT`cAH zAM&L6sl-fQ$0XhP1^!LvF;|M+@s2k}=*ad+&FgiWm9y`i$A9pHj;~mFAQ0yWoKZ&d zh?qB@U%V*k%UE6stI9pYP{Y-3{O7c{vJybuPBww=@p<{fRta^*$A^n@ja(BQl4pK3 zmnC<15TAX-x5w8H7$GA(x}Zd_zjlNL5jOaWdC<%NAJta%)@@#*M}9MKkXGDaZ%j}8 zi5YY~S2A|lAJihNS$G-$OAD|ks1cdrjJNtuA?@_lt(EO7t8^~)NvBfDs3MAc;&>~; zoSBi(F3*`70W>p?t4M4Am3(xJc1=uqoJdR)wY7tUTBJTj>e$CNSKTkrA}++fMUfUm z%y%?x`0Geg&It^aRI!KROLYnV3#u`@$ppAazDPgVHM(v`jbMudW&xCxA`h!`6|?cib1=Z-RnhXCPyQiQjO@9j|8oV z(zl}S=tkc(i5o|TM01`cgT~?Yx`@v3QyViv-@8TZz0F9o>`t5Cf5iDumMJ#y?E}TE zwHLi%wh7(I72aXMYX^B0SJQdwV=gK1Mq9b7Ew{ zImwm#ixNB-No?LLRU|Jh-(ad0haHA!MK;as|90TxE3MbBF$S$jsP=cphAOm!_QoKX z&-*TAQrsng=3w|^EH4^Vu>yNp{r>2=_!EmRL{GK^-Jj(6)|F|4Fx#5?;UC^UhQmg(%#U@)QvTl(%!$@`C3pQO%Es zfAfD$xA2WhAbI_>i_I(1GFcF!#m0jowBO&?p%j<3n**!M?mh@SiJg14|M=R4{dW55 z4H3yE9T`XfiS%&Q3|hO|Y~UpIO$EBN%qhj;(NMMSm)IyPt0 zZbuD{pGuD_)@_miif7G^@^z8+L9|N5Wr1A)qq`yEIoV6Xw2QV3`Sm-x{EksgI0&p# z3=Mh2JvMymYW^PruQ5lt-?nGt#gH_n)F*!~fV511rr6ZxP-vbf=$-x6V&_cBNq7Xw z>S`{xF=v;gL!=s;AqVpo<7(WqbRbb@6gK-v z?LT_88hEG4DTMHEUSEzKa0%Cz+Y(cK1E z*YHi~aoH5z*e6!1{!#KeEjA*cH0aRX1f5UR7^#KbQY-0O?52Z8?e&vbV3=G)J(4vb zqsGRJx(xcu&4+{6T(Yj6(NZ`Ae$aIg} zF@upW2$oqexoAeFwqhE}h$u^VLVAs}x2{`%msJJ)R1K;u;`O*Z>YA>~dP;%6L$l+o ztTE>gG!E?)wR|XR3RF75VMhd!Z$~(&*xQ|CEfLekE0an!b*<<+{ z0`}!ctV8&J$YkehMje8;yUk;qXO3A;ESUI#y;u6Csg$MZ*kM5{i=R_PZ6VFSLObd$ zMQ}FkZ1t;0ZZx?L|ayxhy5$ht2rP_g58HsPg>K#Fssq8%b-(_gMPu)|At zG2uE-D~L{C`#J`Zzr7nD5XE}@l%bfzOC2fwYRS~4G3?xlDri(I7;lzZ1VtQyK`M!`XhKi%5uI9J0rh<&rE(1%LZ<_pW5+6wra0J3! zLyw~=XLW=R;&tPC+yf~~a=QCmw@AIALgfqpUCp7a?@+)-IO_^L$^HTtP*Z9n^aT&2{gqMTJH#a>(3Xm9hn+YQhBL^PnEm-e6ZpT` z#)A!RfhmU-U{f@8-r|6VzY|~*pBFZtlS8$rpjeIhTW;(2ZV%NxGCq5rDlzcVeYGn; z4;nh4U{h6=A9%NG!I|i<`!mK%iVq_W850w<{AHc}DtYyDAuso#vI)y-r&g#aAK{H) zk+y&y&ljbD0L;4=w{Cw9n{xA`ae41a-@z6up}Aj=Ns_iEV8bte1PTR5e9Y;=+=X|G zHCZ6te4#a1I>ViQ*4ugkN{G#R>JupFb-Nr4atG?AM|gI`pxFLAIn;A_hnAyxzT|Jk0<8n>u?r)z4OWqC-;%APhPF-XZGknD&U$ z!=B6}bO#IA%mN`Tb$cH=gw45;ZSBXuU98 zU7@WCs7XQ@tSEs;&R<>vDlIsGYh3Oyak2MINX^L8SEIULP^R)~uG<800;LOZKA-RC zMnv*e*5j{0C2dLdjRycc9(BKpw~||pSd))`H1#57L_8I8=C^xGa?s{QR7=U& z!P10UvHt!;oo0NEIopTwUFn8$%$p%L?Ahiz^(}d#oUU)twHMY)Bad^?Ywxu8=yOFe ztVe5uR8@;CM{Ra=|D`{ZRNDnM(-kQC&zmde{G+B*yB(|MC0WU(a=DE0Mf6vSn!wvN ze5jC{{y%N18SN4Fxc(g9-;4`d5;}=||GF-OvHPV8b;o92&~65}a5|w>JfL-aF2Uox z66pavwBY4$?&MQT-Ie|vVTH>%*Lt+k)&v@|Lmt&ga6433vc$x+DBUC1TMM|ClhEBy zKU0o=SB7-j0!bEyhb1XV@+XT7Od#^v+7Ny|-=+ifCPz=@_1&%?2Vol);k%sfr*S*= z)^a{b%i)LqAU&S$iuJcQj)>@XFN)Mx_ie)(d-Sxe`VkB^CQglM$Tk&WZ;vQXZ66O@ z&;_hB-MsbR9)bUeerYxMVX?J*pGsJfP{iZvN0)jHn;N&E=Dg2ndImk2RHI&43Hb2Q zMpM&qKa*Kj643z(ym5F)dpH|53^aLr@dr$$oHQ;~EoqmuH);onVr8&05&`J~^khq& zNg%%*qoL<)$trD{7>GHig3z!1T_^|c$pb3@I+mTs&dgqv4T9^1osa2%U!%P1)9oLk)S0Q>7_tt^;yQu zn-#~fNxpL9(Ew19?H7)0zST*o!bmT~Q$d??D~QJ1hXaGvzM?d|k84stBOHF15J_A zo0FxE*3=3T%YRwns>@ax=}6Wq_L#ri$O{Jq=3NKb*N}d)p!HN%{tim6;kVseU!T5s z9goWY$h*gSZc|jGs`E~0T;W^R~Jd)V-30?`_*%dMN`6Q6X#A6cF za5%Kj44x;$d2~zE+lBF!_AYCJU|~-uz6Ju6G|}Xl#!0#ti>&cWlc_H1+?7U?k z=QjROOf||re|@S%4o;l)Ab3v<|qDz;v=1WcM zeAJ7$GaD6 zKn`FmKNhcn7#$SuaI3Nfl^0KrPpL#eXiQ|w+ZW*bsvTl?gBCh{s3}hzZcG$zY^q!< zq(lqnFn_MH^4D|4^uLL?KPDweBhfU@d=QT`$28D!=_T_AQ)BJQvF3vjbRf%&!coYRA zl}+y=vY$8=Kkz2jUQS4T_C>G-21~ckP>(78lj9FTkxI1=ImDb)zOVezLk+0 zM@4n-$Tkp>?faDr%R}e2rG6Y8BuJ4TO!F!I6qQgKf4Ex;@hS;qQei-z+I7NT4YI5a z-G+;Eh_Z;C^UYs&v}&T?);N<65_Q~sDfSBI{Xel@2QXDvG*1cJPJbq(V z{jxn87u|1~&z@SVD05|hD;D9#IKIFVO``_eW`U3?f{2Z@?_%Vbzkh+pO-Q z*Y1b$c&KLEr*I9FP?J|j^K-muXEdAvi(|SH-6QMNSuqCNM@&oaa zohx}^82j7!ym~5d`uk5 z;g9IHHm*65lm8fEl_umO{N$J;$u;rSGv!%BZRS{g=;Rj0#e-bLg%m)Lwd9NeQz#yn z!?yfh3(}e1KZCK}kx(u7mT-B^sRW;Xtrb`ylnn#Ja?^sYTkmzwb4w6qj;F3)*PBaf zj>*X*<9K>1f=u3{wRHUqs#p>?Zr#YQU<#mnc}bLzCZOTMH;8n_gIpg$CK=#MQz=m2 zC8V1v0T-kzxVW&NzBO6#`6dJ=!p=f1te6W%)LjS^4KnKj)pl6IiKPQRxqi5ZGu@NS zq2O0zloQRg<2YetIBQt}w3@jcz~k<>lR!-2A>te~h65=j;XJ z%v+S>v@$>z9C*5hDN&yRm3{r;FD7oK$vVw>ZJGZN_-QakMl=0G)KzRY+eafJpx<9D zQOTau6ZLun=S0TPQz+X|V2KqJnr%{8zR`eQ@4BoPPEPU!&=otRnl`J-G=g_fJix5A zquZ|qoJc)K;O3Y*FG4qo)>aQA-F&WR+Dr)&C6IUY)%xXLZrmtc?s~5K*OWj%rz4#@ z?v~q3U%~j08G%~Z@d|pK|4~JD*0Y)n0J6+6eF4{^nFn_yM0u69AK~iH#dn^@4n%|M-{{OQ zAgbeN^>B?m{bKc@Se5@`nPN{+F%VuU8=JJ$PZR?$p2J|j^zm(d3q-etU$bB&BYume zrN_sh=JCrJV?%)d-^pY4W`+bu_QA|v$AFAqc* z<^Vrh{z?=7^)mS1%K|Y?LG|aB&#tGEJ^HwQ=AHDmQCnWG-?b##f>Ex2e_~Mo+spqs z6Qcp^pI`-yl|cP}Z-RyTe+vTJE&spS4AF}7!eBy5MNCr4HRea65=W%Pyy5jPZY zmyYdOW1U&20Uz)Ys%Rg>s&IHae_6yvmk-0Ra+i!Z3PAwoyTYf59LMD^Jf6m;zXc%U znJUE29vzeoU!cRk=O7OR6+GHeJmI|PbUA^)^Hd0Fk}6Pkdhg0bky|!jsN*e{|M&`_ z2GzZo2}TrhMilGBNoaJ+?l(~V%X7lj;GfV_xq2f{M`s%Ws{lF}^)i_5ICnB$NQSw; z8j=xG|7dRup-Nm5PReV}?*TirUnCC?YKD|A#@f_B05d*plsA#4Mx5a!} zpN&&~20Dym)h@AYalo@ooiTxS%=UxqUjVJ(ahxG+GcofQ5reB_rH9j>6z^hiHoOF3 zZ3ov*edB=DJnInz_HSExw#jZR{h+RG6~~D)-XjaRqbTcP+{h|a^NNjzh%9?Ii|sgi zb$%uUSvTBeEnB3d8=0K^F$f2^fT-nUz#^1X7l{GA!pf%~UGBP+Vun=;^PU|qNHN5$ z3Pr&UGTL3piMjNZfQ6kbJo#oCu=o?r@x)gBs~4-8QZC(lE)a;=A>C3ttbnKnWc)Kg zcBdbJgDh*m0ferJ@pWx;|Ei#n)Q36>2RVj!(ZThZ9_CHdGB1wixi_Z8rcjR=#761m zQP2toV6Xjhw6(tgA+Vhr+N+;kN1biwhJBfe+w{zIuen5Wg+6NHj>P;u)c2X6!~sG! zEs9lyyS`JDA;%d7sDU^708t^u?Xb;I3OkZr5;rR#)K|;w#XteTDm@|i*Gr7`Aj_*P zdjyhPQ7tFY?|Mj&eX2ViNt2R2C9|H>^__sP$+Bnuswd>!eCxfAMOqr`#4{{a)baKr zqQ_mAyvy60mucULJ|GhdY&)<|;zhvVksUQV*cN)>0XA^whCXRH)h4LvY75IhSg`}v zIF_?6+ffZ#K|`(oN)y(>Snb}XZ%Q_6)Jrbu)dpU%TxboIhVyk3JuWs&opE!b1ZD1Jo)Y~V#i z59?=cP`LOg)d)4p;7z*Y&- z9mS^s^U+NIYVq1nV<$u<`h=`T`%oX|>8uvwqcGEx0|Ck2F8C@vh9dH?HWfdOYYa%C z&2Z6Kl!;b%>#wl`TunM@3m z1V6}OnH69~VPRV+4S@jly~bQH$OVtD8p)KEx!$ta%E%ussBkz1Xa&k1GT~i=ii`f$ z59<1dYvr8$Cmj>oA)4uPKCdMlJ9+MTxfqiPB0{J|u}M}R1!zT9{-E9CRSb;VtyEAb zAspEssZ5d7gDNkml0s~H;DnyiE|SiSM)r+y3%S(O+k$$~kY~@O>F|JDk;Yl}-csFu7<4GQX z;IeiYB_|o&RZu(!wV)JYd@_1##m>LfBa{bfZ!r1*V6Wp-p>w@W=qo_@dew1D;0--` z3hHDt!;zp}4YGJU|V1}Sma~qwu`OknC$Sy!? z^sQQxm9c{|JJv7Su~};#Mg}?z7{2+Cg(|M$U5k*YBX^_Y zG!g8i-%v64oFO_7nAufRpK!hkU;1?JePda6jwz)AQji!HCCO$`!U6dL^ARuLl8!f< zdY>}n+@U=(kru0meP}2Ri{5;9*rL$llZZr3>r(RlH7zwm;aNeUqnCJFnRtK;70=o@)-0gd%q*MN4KJ7Cwq9k{YDKones%#*H=5Zv z;wYSI6*mPgp=FBnecoQ&G-M`(lgOF|9PGr$1DJFH?P!oU3M2UCJ7qUc8)3S3M;%i0Q7hr{ZZnGF;}CBIzR{dW}DfiOpfC4{-v-2IvYG>$+JK-*0<(eV2s zUY5nY6GdwJ`MQZ9*dQ_S1O}MH>F4aZc=GB3fBm;>NFpaM8A_0X8U#ij6k*D>Co`EG z&wFE_?8XlkB_9L&tJ9Ssm#hN145&XKyhTFClD|3sqZ|^+H=%nG#|x6GKXLmP#2MUL z{02>K_UT(h=bcU^nQBxCR6q(rEl@nBtwp7it*br_$sTwd_w!s>e0TiZv0;a!0h~hG zRQ+AdkI$w34_KP_1{swUgxgCWk*x74wg&zRxt@b^K073pzObw6@T15@n^il&;38E?G2OCPc2oO`Xn0?gdk=nsti|O} zecV>cqvgh!9@ z3$#L`Q4T1Zn$%ldUr5afd&su@s(;e?LB6$#6c9Y$q6hVA)oo{{#G={eRqIRmG%l8h z6JY|<#zgd?2YMXs4bjK^thVY44x5#Q#-9Y}M{J z5HBaV(9U4+|7Gk)Y+b_b4-_{FN-Mq~aNXWCgx8Yteus5si-oximKPi1n{FA&AF6)* zQnGqI54$E66>H&w91Tx3(4pC}?XlpO5e>oQcU^$VWMb2&zoRM;rvqCs{RXuAjS!D& zbAFq3R%m^BbA>jRb(~`s=sJP(T!KLUa+1A(lSIz^adEX(cEkI&vF*B18~zgdScS-% z14~!Q`rovIgMC3`>Gx5h+z{nge?`PK6DK^OV8Y-?Jo~q~F;xXVK!qtuhFhB zc>sC~5+uA_Sg@!nUZi3jR&X}snO1Y*60mid=jz9|*yL{W6%!VREDs1bZ<2G~LZ{As zZ3zEQGYLj6bPnHH*|t>wW7cQxy}yW09)hAo@xV|Q=*;%7g~gPp1=R8jbsmOYG#J^< z!I`BkJj!c~8ld8cpm6iQF?e_p^%vsaOEVw-(RjoiUbQ>R*dpndN88(%r#DL6O3H{p zo6wtkFXVnfPM{Wd@#vN>f;ceqVws96+MoR?RZF5l@(1miC60Kana0wVwjJ|gg_+9I z{i&%lm8#y^h`eM9QC_Ogkj$B}1&GqJ5JF%BAwu92TB<9r^uA5~1D@Af2jTvQ0-Lvy zETzfB7!6vXjlE7qRmcc~SLsSsrX)96lkdT(%*T*(6J@0N!1+kAU+S#6o5PPSz-YE~ z9RU%KS=t`_Vc*BrF!x{t1`9fTZhnxZU3<2OO=(7Q96y+&&d-4t=1#1z|0Q0*j3R9* zT_-dpA(CC5Ix6b4Jz;bdUm!b0RDa>c-usr)5^y+c@zsi-%h@vhZ+oOur_L^}=b=r2 zb5ked+Yq_+7~NL(O8e$%RL`f=KnD+Db)4ezt<9``5T_y0oQD?Iwm_Cz!)1&JC)Y0Q zv%fiPc@I`Hr~C}<3Hlq7029GXW|=~9(bh`1a@Ze~S?Ad-?nJ@dwSJhBL`ew7u@1Ai z9ds?_=INmN^<3*%J1?9^Q54RQ2aDy8tYvbYC*?ym?HSrIqFP&YJzS(yM|sHkv?HiL zn85cjf5k|ncv$-SQt*?CtRF#COAFX0;k_REX&Ez!BL}kY zz%VDYI6pKnn6{t3v@gEe;M2S=V?90V0UKx)f{8hss7^VQLKb=X!uqnl zC{vYwtk_cOqa&(asQK}aKC`jfg@+4Xbj;*=bkp;zs!P_h&j7fHCuA}q%3!VKcgVh# z3Qci~9?azoI!Z=hI`H;9ghz&oJD8nZ{9K)D%A?|ZSDz9r$}FMo^Rn}?R)vcsp5PTT z9Hob+@|ej}gHHZ(aLx8govZUa_Admgv=&R`1u4wIp>dz#m8h}zERtm>7e3KjG%s1M z82|BbZRA-SSD}}dLBitd=y`)oNJk@jt9`V1o&{P`qN%?*@icX(CQ&v*&ziir#{plt^NejJB~0dm%? zb=F|FNK8CC?jO!0r&rbHKv=OHt#EZ3@#CV^->KI-SqBEU$D`rfFre@2<@tg4HHowC zOv{p?T0sQziBEo!WmRaSiz9*P@DN{^$$gFHBERXu)x;BfBq7H<68Yv9TtL#USvg?O z*1yCwH1K$@vJe_RA+U=uegr1xi900SIO<}{{hEP7Nqe7B{ZLw5%vUYJp!9Oa(X|}$ z^MXt;hXjB6^(|Uo#QZtrTVXBysP|JesJ7Q)Ojr`GSPVZe1T+&E8hVx=s(-IfQ9S7C+UUN$dLkmMbzH~q}9LTz^kHz zH+UCrl0m(Zt1>)mUrd&9=Rsn!%Z;BF94PlG5%DWV-ij0-CPAjy%xiz=Iqzb;foxfe zG*5xF?qSuEmDGR)AvrNw&!Q`J-8MLP`%5GE``fJxZY4z)D-tXgt{PVjch{*g_~QXX zzaia+YU%{4)cz6JMfC3lf$*-c_DC6pSW>dSYfu)R%Y=q3?$sn`ZFX%dy2%fc_li6Y z-efq~A&&TO`I5E4tbNm&?P1-z8fWjNj1F~ccem)m8chQ8mt_=e1!d^URyH1~)3@5qF-X}mzwr)XgA>nt{V zMI=!;E$Bvpu%#bCUuDtX$s%)uSIoe}NU0^o(+;(qp)w{>M0~8u!d~4E*S$#JuA5z4 z+g5Pd$}_5V-eR+u+LV?>>-;mvMudHUTqE=ON3tl_O&}6%J?ZCphpxY>U-%<7 zc8XAmnW^Al{34stu#}!4cYKOjZBwS#)-LxLCkI-T7?4OA_ybKIRR+yEFZq<+t7TgxBCia zf{g9>`fqVq*#sfZ{1IGRI zwT}(~^%~~#>}xVG|LPfYC+=Gl@%3dj7_FzU*uxMYN{iI1ol4du){uMh;x>{PSM2@5 zx1OR5igpmazyl|J^fKXjy8?Zm=L{WG+=|04tkDDqoO@}HRAz17)izDuyPQq4HAaj@ zxE;{+V9LPkJeooK^iDi!D#O!ChP%*Qvz(~}Ys#_YMIq6wiT?JG>8HiCAWEl=tWps$ zmjb<1mn;gzHSs46P$3I~u8Cpnrl_y0MY8$tRVI8P*68=m+!V;;%Rr;ry+%BK>1H3l z>S(PMuZIDJttwU^V5QKG1J(aI8W$_w`^{Qy-QB z>2R;}bNDO!1+bjpRKL!A3j#6bSOX_TqWilgTl=P5!@0P6>wVQ~^HEFohp>#-$J2yC zLs2JhcWqx(J6+HIRHBlzF6K~&NXUa0w!qO`H!O0=WAJ>r>7cmIrY z^|5-(aC-=(l^e(v%Vro3&Y_v?YCN&rYQ8$YzorH&+4O>m3JP5Bq^nM`N4&THhmcpZOm&){6gU?jV)@2{sIKAn|;UI}>Y zb%~M>H-+Z9GcI0Du3M)q8SW~U&Y3?a0yunYnWr4{e%zSn+BACqNEC($Ux&P6=R`#< zaU9=9pN&`bu^{Z$e^<-`@*K3g<_h?aX8;%Zg~j3&sfJ6;%^9DZ?fp4CbX#WshWa1T zeqe4nM6KE3@ZAe?BU5v;KCv>(Lj~DuMF+5mkr}0;qSaiB+R8}B_7&e5YW@7sC`$7L zelYRy@dRT&f;?9|j_FW%Ki|@mlCL=`Fzv}dsSY3tuH+T<>?pAu17g3?W`&+-G!f&0 ziu!P$HhR{?*~u%*`~_qKjHlRna66kugCRy5$c(HCyFwgn4=UOmRU00o#a!j51(_2rt7{x`S2gq zC(r(+N8!mXLiK8~N-lW1VFm!L85Q7OB$TWd5o|FzZ|H9yqbLq_in#=yR9b=7^2IOp zeNp}AyTUH4GQTO3x&*lV-yj03{jc5y{VGVNzeQyUWpI1}B40*uHCMDfvm@Q&2Lb;YqCC{$DRTASUhbb|8DVrPDAb z(d1%3xX)}M$k`7hO0H(@TC(TD#$PPSlCOqM^@vF#H=A6QlF&0D8lG{JAsq?nX-e>{ z2EzkOi({}Ndu+66f&KOB(i?J(A|M8m+3qa} zdg3ue|85<^cQi=sPF5!^<+ZxVv<1p#ZkZ@mSt`3&(*`xmgm{-IEoISZEiNP}l2Kvo zT-lI{h$Z%?qNmilZzJ7=hh3&U_5wrcuR41EOXh1zl{QTZLCdBkxjcjw3rGCk1O>*< znv+gQfG^*YZjd(Whp&PFZKols5M5H~Z0=DRPBOj{3d&2wpUYT>H~g)|D6Gmh>(@=>`?55rjZI*g&}V71!-S z-Q*05#|2( z_Hj=#Pj$R-AsuRYJ!#mZz3J{mh_RM;tJxiWi7qRE@h6i`ml^$LMkA2fY zs-X+`oh=Td7>L(j&Uuzo)v5gi^~75LLquZ?u%d-`j>Cj>@op^yt})sIFQ4PAtYH{n^B#IyMqTds{`yrRo*l=X z+2&i^fCk1khfhO=Owvu3+a|540wiAQ2 z0|A_d3RsX*2gNTdH!Hdk7U97*+I*SkO_aptt@$MLx&tU78-6ou*IVtb6qM3T*Vl7x zs8AZ$KnAw&j#sP~#=NjX=2`EK`1{AE=40D+Gf+`NKw;6$@F>o*&ZCJOZZX;&lcRn8 zboSEDJp0>FQN5SwKhYi?J89%eEqsigR0H55jPz~YdYhkyJg^g>&wlckS@$IyI&Nov z(G1deOnB@`R%Zs#yRtPdb|)K$xuoi99hk^;`u7&1b>9gMfr09g@$T-GiKP_% zhuC8smw}!3CV`h~evyn98?bIko}s(T^J#}^=H9RyR}pdDJhzTM_wq-OZ|2EsnZ*22 z&2!(Zo7sXo!!7y!813rbtQB@le0?EHK^%N6rXld05Uie{ARk*@J|V0iqj7Afc~&Ef z1NffF!;yVGmk>lR>ueRF?AkH};6uc#YDN)@K$+$q7E=Ec%K6R32fJLLN>_9uMiI_VYBxp%Ngn{zohaRI{Y}*UW&No6!Ho4*%~@!p=?MSxZSC zW%5g-u-Vpxeh}B-`xFG#is*3;t~P|9@^Zs z{c}umcnh`@j6PWc*@zQm6wvr)&ez2iLE`zi{BK3!O@aTlSt7s06t19A^@Q$2lQzlv zAq;sJ!NE%~#+@m;!4eyes58FxNpLX}Cr}-hA z;c4KGEyhT|8!_-T`cLWYzWM#b$Y$tfR?U+jM3B;X^bMJ< zHh^f!29GO9hl(2oOpT#S>4*RYpc)~D$3S-k|98MY-1nE#At>|di8S0i9XV(8ADT#I zqYpAZrqiv1`>Nx9%qeDU6Q-bHa_v4n7QnalfrO9}Hu8b45#F8S-#ZGA#nSdL2GY25 ztVB?QhA;&~fHa6Z$p<^wA^7~%lcF|CE6{cCEh$Rt}L zx&c0f1`ZqG3MTNM|1$&V|HPpD|MNBfoscP{5p76Sb;bjNZ)m2cUl-fs!G(2l^+`UE zf;k4}P__ngCm8t6V=w<5(kUv~p3aN}^EO~yjXm?p){yR`b`Fw+rho^J7!CP$EjZv( z`>CE#eB6ngqL^em9R`U|fMy{7JHX`F#_UsL7kEEH zgY(W80Zh2JGxjpgs(CFG-ZTB(wKUqPmo{XQ;ur9wvd%1{XF_(U)lAmdiwT_VkvS6OS3VN7k9~Py`T9?zrG2V}sg8Uz zB0&1h@f3;9UJ$FOoWNoj;lHo|$Oo}>MxO+=F;`ou7{Fs@p9#vveJ8;}uC3ffeG#CP zNPQ@F_sLV5!{?39rzX#aVu$Hc2WZtRta{eC?lKmb!&$oQkRtl)fO>qZ-G#S819Y$D zPTEKa$6J?-2S4HwBxm`DEQhHx(!Q8^0z`gk{6;``%9!bwHIV7Oj+Qo@jd!B&z3G@# zM1qh|97IsL6J`&O@Ehd@>7A^z;jd(W>intlQ7+Ht#Ggz#s#))E0=~})Z1JbV`&34P zqR&o4={VsCUy}-psG!S8=ebUwR&$af8gAyt9UPr-_;ncKWjVQ^6dFoTr2H)LBUwfs zE~c&$wso>?=Izl}xwb?!4F5@dWw4+{<2{Xj$MCuA=@?5lr06visIC?5_-ejjd0bx} z&i&B2*sHb%(TG9F4b*cyAXL$8XTY|v7z|i4yvrQ#6=TG|7k{3j^MG*Xw7T`Zuj?nx zW`9A{E{f@SXYaRK#W0lRxiELCG%N#bnFcFzhb`U{k3)a#h-R#3jhClPWPUF!W_rY4 zRa(7T=X!=dn0CBtE^{&vv$nFg8txCxifk`ij42HiJemANvKP{$$#J>{DsnqC73z;| zy*@c}=#lqVl$OjcY4~YoN6Mhzz=C#EHriuOe7PA}T1JBWl?;O`MMMI?7I8B;o?&ni zgNgd7mQbP~7S}yn#6c1oNmxzmFzT9@v&~fch8{0E@;D$R4G3(r5%Xsy=8)oYlv_x@ zYvD*ICma|k>s;958e1~Lac?e_L}`YNr}cdede=PS@Ds_=CG-S=>G=!(mn0yG3GXPL zW>QmgogTfgq2GdREztMjSdJleivj*Ve&oMHh-FA>dutr;!48KbC+Wv@nAffC0cH9# z%Z~G)?_UHaP|8Ue@)@q_?miG&kp0_W9%BsdUw~BYhMLaPyz+Ce65xi|p z?@>fx1kajg1jSAXYc55F#;%S`YS3|eeQ9h^_?qJMuo*5obIDpk@Pz2vT9dT1oi3$gI>ZzCUBO?lG9z#|fT=Z;!^4^&72zKC~Q`&kxa? z5^s4Tak)`aQ~VTcX281LrgNHG%#CzeGj)X@-Dg^z7=4`KqnNS%1ttVtY7K>EI%#Oc ziHL*Cb~D#*#w4{d!iah&|%o)P?)obs~C6PuRSt3Q#cV8Ufmqw^^2(ZV`yGO2^$3J4WFcP9Fi}zRDRBQ zQtIfhG1t>K8Bo?D)Yf`AJ|ZPb{K$45zMQz>%gxtkIh-f~cEv&J4JTgitJT%w;7-VA z4So}UX$E-jsM(feKLiWdx40bx#8o2ZkG{B+9I zvn*_7sn?&GAlc=Wm@JgHNF~X=M*2=Tuk_SB?oLgBsThSCY*2T%tQ+da<3&>&V5U!_ zPw~{OTAXX$x;wKgZCvQiVn7Z@-rU+LAp#XKob5G4ln??;#e;$8rTzK7QgaFF#-{^y zpkCRiJu8>!$g|@97-f3|2#^V}@wYE(Jec*B^0=C$YjD%&F;hYn1J)SqJXby0JK0gz zk{vC53>i{J>x5YX)`Qc>Al*nTnlZM~u8#ZI#RXFw& zAVIQvKYM|02i+G^f0Xntwz)#F1E%lCE?@s(M{wxjSa1o{LRo=hXyI_YJeAW;y%hDw z%E%&>WDAqu+%)daDQ@Gv$r8n)s#;90SVF=|6qEf|NQ}0o_!xM;>xc&QOqZ~FiI10A z{uCr{bLw-v^yK+DC?cM&Aq>TxT_4UmzW;U7Q+)Czb=zJfbmS-tT;VgK!H_&wpXw2% zK*7OyKI8Qouv%!5>y0m2UVy!N#+W~y#qIV-FN%exwm!rJy^buf&j_EY>^gb^S$)Kg zitW&uq$&Y`O_l;KzX&M3Pj4Dk!m)6P1|uG+B;IxrYb^qn|GZa3*g?rG%5GI2IOZXGFvmPl(#Ep)4e zd`2V$>wl$-v;d`_GgYcKvin}~TYqz(<4cUymqG)acEQAbeO|E2`bkdb*AQ?u9T7}i zarTD0T@OjbroURzhq~qUVI}CJiy~+z@r!3e*BQKlv0TKQb|VCcT&C3tRn@mV2noX53#PjoPV7$63m-RaS~s+J5TtTyyc52s?65K_Mg87jOVB=#;SdntC@S!QP>L~^qm~y-kj-ysR3=N z*&5{?bDpT%d#hF=<*VA99apCpSFFueY*iZ=uV{Vva5_gx)6%HM^aV59@%>8U0@E34 zO>$4p*gM@|p~DtNeV;i6niTZc0<23M=CzOQMLx~b1xGEm*wB~D?qJojD`5%0_E3}{ z1M(3$lkRauaS`V|Cj2qnqLi$aId|>~E=A&1|iR*4HF* zwdz z^on#8nTfrhCpqXr9??3c6UDTphQ=>TI^Tl`;-Fu`#i%A-6+s6klvaac2HihJ%576V zBZ`78(osg>nBlsb;M5z#(X97Mq?FoUnNjIu-#j%_M(^5}6Nftv{(d8U_3axMsLf2H zQ@NGjy=z<-^L0yUhU=H{NkqNI{1vl z^N%m7y&8+)?k#ZVkI!(3PUc-3hFXYP8gkLHZa;;fIWA5nWK5%DjBNTcVh$fPTucjF z*mB3vQ;imh8ll^5)G%LFz)A`t7bB~o z@B+IHtJy#F8-}NUKcee`I{s03$N>cc0r0DfdI>I%~QQ5Z8dQerx+;`0ti**El-|KOu}xc%|^O#PcLEcKl=!2RFR7I$rq0qVmt0rVAD- zAtYJUqQ>An_|8h{_{yt^ltM7b^RugD!`ZQ=qGgi9hiOC3-0aeE@}BWV_NM%tVY{uV zv|p*}E$yq{t||cvOEUWj@M*zL;PC}s=G6myWV*5>3Dw+Ze z1R_KorrNM{wnJSDbgb z80Yc*^$CK|97R64vOn1#7F&Me7=G$-Kz`(5%C zNz&E4p2YJdNC1L(rJF7eZFQc%68o&qyB1%6PFL}*(`U#jC8X^#LL?02<|KQl?W)hQ z5I||0UJwpBObe`}HpXAB1(G-)1Gl4dVrTWzev3ILts2i>c+Nk*^dl*_<&ZRs$YQql z>XGG`$xnQhSj?nWD*Q><8lG}$_JpjnUewk1Ug?tAHdFfhni5mCI<^?1H-RDJ^9==z z%Hyq&O87F9a_`$POZKQc1~h%2z9kmcWv#bM8ZzomzH(#A;q<%1+C>BysXuQ>sT1F1 z^OHC(97!9^AgZp`-CK~CjUvKjn45I<(*2+5i#dHIkY}4ACySQ0nRAmKD`+dC0FY2L z06;CNh~%bHrr5K`g4G)czrjhqpk(PsSCCO7mN$_XNmNXI-1hU zCeOdLv75cr^Jmr_a7G6vzuX#sh^D1hF^<1YAXHdpP-S*5_6NlL_GU*5R`6SayroyQ z_GWU1oty?Z%rcIqqX&hori3m>O!{giz03NGF<=oN`brS&NY{$9d1QR=$$)2Q_YQ|V z$q8`0+YX5<6Fb|kj1Ih_g6gA~zqmU(5r`PvwnRxOnvmTJ+fscWVK407c|FMr5#`{e zY`1&`%eyY8wBLFCJ`|*vXq)C`b_}P88UJI=rKb@=3De9wEy>lXEwkQA_RIcEjh_?fwEG zRBlA5*XeP`kk!(#>Q*1*n|Jzby4G2$@0Yih=4U>v&wR1BYk>Q;@2J%|;eP0w>lW5}9L!TN?FlRjqalnAZaDevb z%Kz-xV%A5l`qM0f^k}8&YCN!C56CcFz-%q}6cwihp08L`E_6D zii>Rt-y2?>zBjoVauBC-DIae2{5?P;oWj24tpw9cB4?eszbM%2lTX1Lw{SM-{oUiG zvnRT2EW`#8_$vA%P$R6m7;!;*IFn#%@BTON(;7T~%XXrkGK~&(&2o(P!(XTFj9=qs zdFzbsl3OSjejwer5_4CloIb^&8aQKTw&u~;MhO55SYX)@g!=g$VIJ!_NrkP2grC+$ zOvLF`I%eJN#G&g_T{xz5cd48>>ZyBQ)zii55F67`!N>5K*A2Enyg_6~9v#DL`zUmz zwOQgWqY}hH&x>_(Ts|2=Q8`M#5Yl+)dzleJvb9PibiycqjYM`C-LnH5ga~Saz#~yn zU0mBGV;gEX5*aoKfW!~VCPQ&Yjdnb=LAsz))iZ!k#HCyb+^Ft)!8Js&CgZW1S=)?+ zv>)1am99E_zpcmEb7cs!LOeY6Yc58@;Um{0= zLoq%%4IH9yfI>golIjHTLm8B(OWG+$UUo4=t0(!m`pW#GAiLL;13aV z%{bQ9-A8t6jgekiI50^GDNu``Kk(V&LgkqZaItr$@q5B--|3FCG9Uj_nwSwJIY1o&^1b?Xqu%|) zY~dFpDEMnl7r{g&<+H|%jFZ<6V0+CL<;@VC4IbJt$dJKblB2u%Z~uzE=6hcugabhB z{WX>Mn|GB!#Wh(88^5&VC;?|9BEnH*&ew(0{mbEw&qz^rDQ=mq*Qo*m$mnIZxza*b z4pZD$9cHvTPuXmhIeLfXWl{?Y`enRU2Di2%o4=l2=nrL)Pnf)~8w8HGa9}&z(j`nl zTE??u0n|3#UuPzs;{5@9EE16i29)VDZJ^8jEShWwsZjwPq?thk8?aCjD`RUy_P8|z zaj&Qveb=Gb&@k_eIOwD9(ZhlVDK`@$ZK<<%Q(PEbrY7$a?CIhkd9stMzR5t70?BF{ z>Coo2B=2{CC8FU=XF+Clqx>HDfD|KE3+AQ!gN2fA;%t7R{OE6@*iNpipAEa5h63j# zJos7(Ii=Fy#}UK6Z16|X%EZ?RWa!-=_R}dBh=;Wv@}HY830i6_x(oZ0Pd{ICgQ$T8 zbZ`IC?>JD7qet;Eg&@MxE$s4Bm_z#3%UXAmXSx41;LT zy1C22K9VI3$=Z>PLQpNlyok(=cy8gQOh1Umm2!F>*{ikozzcdaZ|6MDWvxU!1Hx3di$Yre zeHY%?zmBT?nSw4}xAmv_Ic!#>H6Hs1cOL%Taz59p$qm1Y0p{4izVhzoA0baA0b09| zaa5lxYb?G4mad_QkFpeNanuG(9u3S!`??6JdT-;Fv677CwH@O`clXlfE3X@u9Tsny z;hANKJ(J5ehJc=CSVi4BNcaV$94hf$P?4=g7+esd>F4nSlZTLFJGth+BphJxKiHo; zg#+?gU6X1NSQ)s4ylk>UDiPqkOl9x&&{ot&zI{CptO@aIF^jSfO;~ylK-8qS5X*5U}+K_fLQ~`I~Re$4zx`* zSKgM#`+Ac&WQ7P+g+&i6*j&S)CDPrPIyO)pBBLM^kT4{!# zsx7c*(_n%_{QIht>x3p(d|Jbi-R~ZG*`aO8H{-_ea{Go=Kw#jw0I4pf>2E<*J`m!r zE12CNmXzG;2G}1_%G4l$@`b>w2&A27ip!be1NV$R220G!7zf}I?KD#ZLO1zJZ#F(( znwGvRAqd3r+g0GL+vk9T*zcE=G{2Cd`P*X}cfW#TUK&h>D_|*N;3?=g+7l6E=)9){ zh>%0N-oo-(Fl$48%NYT|_yCZ(;C5H&J{Y|VO!So{D)Nx9d>{i1BNcvAz`4 zGQ}PP^+z1{AO*&c%~6(C-kz@24W46{o-d66*so+3j2URX8;iuH6e~+RGQaQ@J}A3N zHoI#;3kLxF7dgb^WUU)y&n3f@8j#X7E!DW&<`bg!D3_x-{0~|uUg3@#!bi(QCy$Bw zRwu2?wpZve`;8C?@BsP?ZN3!kk85aqy=9H>=r|rvJ-R#Yj#K_OO3z+bNWcl*9b&UZ zY{Utlf2*m#pL8kOU!p$67SG-_@ASmw*mMBMz zCt_nx{q&VQKqb1lO#SdZ z;(j_)7wQ(_DGSSkj>^fT%l0fT$6J@A%OW$dBbYoA-ZvyigNo@8l9>0n7E_yiu)Hg! zJzdg_W2@Q0ik?!>R6CXPVus_#2QMA~ijoef=k{Cl&R;fmjw+B@%}GlbU&`q(Rm-B| zeStb?-fsxla-jSd5Cy9Q;8v0QbAePHA?o-qZCgo`xj2;D=m2{5T}=@%NN}?59dbua zaq~=k08*P3U_rYUBx3TD-O^vF#!$Jh!}y~Zu#>@8cHXftJ%Vg5*Okju^2sET&km7& z+`)g1v%mEiY>)t>2FH30#(ZUFZsyd1e zZ)}?2eMw3+j9GLq47H)CYL@Rlncv;M>vN0WC4e8wUoiVrmboW2yqtrMe zzCD<`+j1zWyRh8R)2>;}dA~O2?Cb6?E$u;D#U1|8!lDetfE-tm;QAl;Wv(_@lhpuf zlj9MK&RgAa=0oG8*EINuYwSwuenf*i7YMv2#;8_gH6CY^2*4FUi|BEoGu%i(6nMVT zaCA)%lnN@7yMS+aT;Up4d0~MDD7rlWK71&e8acRlT7V`is$zC#o|T3d z3v8KQHlU5=*q9=kY$<%i+Y zYVsMJz|^4=B+3yh17PxC0R}`$p0=fQ29HIu6b7&a0NKOK+bspaSL|BQizzYdHwG9; z1v08=uo|xbo)22)Ldrv(5Z=!9DJDs z?vK2FOWZ2RQ8-2Jj!n#e1i(gm$r~}+7cp*a=N&esKDRP8f1MF>21kOVWc9A7ARUSR zUjsce^7`C`+tI?5)xy29vVhN6>Svah4bfFHV&hWMSdEd{e9g#h%X`tIBQ$g{*@u!Q zPzKC8m3204qVi%}o9hMN%y$sG%Nn9BSzHllCC``Jc>+iNI6if&~vC8cX}8dnuR2vPji)H2d6 zhY|z==M+C*(X^2{H#H@;!>Fws~j(B$RKia z|4-;F$j1-Jm~8*)*8h>t;v!UUwEQuaXxVkq1NfkbklWKii9+kCNPxfne@%4J4!M(6 zX8=g{{+7=UfVSExPQ$I-EuyDsjkSp)Gz-=F#Ze~X>56~b%7c22uhd=l_u7AA^kpzY zN0Wi~p?HPutkKczhfM_K$ZE&8WJAb=U%S$+5ZsIE6Bkx;;|2_1N54(=*6J)^z(#dBC#TpkUn=gjr% z`|H#qh>%Yh|-_JHHgcrT+RyWsj`X;`Pf8qQ^)#*3l@b0A;BiRZ;Fpc4l1`Q)# zDtF#+rPzi{7B3My<~P#}X3tRpqGdnd_Xw+w^dNV;Xi#n0}jfsyyVeA_~Ay* z+SU9Z3|&L+?)NtcF2r%~tc=P4{83QA060AFGx$kL9i8?wGsfm@yd6e3%KEt<=3|Hn zZa4+|$Rg-&Kc$j{4;Z?Ga4~2-7y>tnE4~F!N{$x%;E6~{VqD;YR6zNk0OlRo=JYsk z6`pghk%SJLC3}0`xGPcB^^HzCjhap)bq7zxgM7ki!02R)tG)PIr^(b!xF zh&N9F{|3>hmLFh;JW@C6tm>-LDJk)HX+R?BAI9*gTX_+3fN8Zc;0e7|pAKePS{kPd z#x`e2%D3$Xl>T1D$G8~_3$=ASV}_<|o(6z21g1>v#Vl3luYUg?a8H;)S!`en%Cjb>c*F<#A`zxM?xxteFc<8A%c zKI88X2*2qOWnTvbv+`QJZA9vIK#MKZR4h2b9HgEMcR_%FR9#)2aXpx|xT3;o1 zO*Dl}zax50WF3~cIMTCl;%Fx{9QH`@1RT_#X6~csy`c;**Q-AM#i?5!W%0<|!caKx z-E_}bMKZmo6NjGYtp0b=iggBwF*jPFToIRC64SrKVQ9GiFs(#Mbu(IZ1i?-LgA1cQovK zyoJsWdhV7poKob3CyQJqi&VS*IFvtVt60;kDlv<$Rqi*%R#uhrY@5T zld4N|Lz0nJ0TK*rD>oL>n52z|{n(O&$%JoG^}3YyyXU)of;TR4y6l!#znK?3^eW#7 zx=%K9VQAbsTYR@%`1BeF1p?{#bOCs_Joa9!>21gJ1zxMQ$_Mfz3r&iZV{3}8)<3b$ z(upK$WLnH_Vw(u%sQ!{Z(|oh< zNal6fKilUXI%_vK>5X#aSE_yL`0A45{_wM(`zi`8(_o3HXp@*4ci(exn9f>3q^>lU z*j}1o*+dYlsa<=XXgAu@dhm(fvVlWQS=6OZ!#Dz2M@n~AMD$*(3FmRgI?|Okh{~=>{Ug-Gt7p9XO~&uWEQ8Cf%Vg& z@o!JU^F=Q2K=eOcNl9Px|A=KKFZ0VFc~%MyTi4AjQ1weqIqM-pAP`PTsGeT=u4g61 z!ktX&+1348s!-WL+djEBHt*ip`2Fb9_CK5KzRpTT=rU$jhc&c>u`O~BUhY}3Sr(Jq zx>?|+-uh)KC)Bi_7cT8#44Ti$Z}QuT782CQk3Bf*9E{styb-;FxhZA}ylZobJ8WIE z((^XWS-RN{kLAsr8A!)i)YZ!EimxJU!gXfvPo?#QMl#7A|?@w9!&Mskj zX4b9X(>7FxYHJaEY`#`O_s6Hdf1P>P{w+tp!IPUMRWvxhhe##D*EDn z>jX*b`T+LJ?zC0wWCF$On|~@tCf9=`}K`H?=%!|YNWNl=DbY!dUS;2 zAa0e`Ice$dUx=s`1QMDb9ooM0xqV&)V+HSGK61fZxOp)}GU)PY(X)JDE>eZR%Sz>i zJ?q|687#t@rm&j0zSyfh%n;O?X>1oHCN|r-TIQ(1X6m8r89nwCZgK^R?B(oJmw~H) z^1Y@eNZ&8!FizVD7rLhH62ztp8~El(UrxF%w7gSner13}=kw=#K_MH-#O$!`y!Q{e zK@Gbdw+DB7ea~F&JSXnIyM3+NZB9|Xq;kJzYqZNeMSo!DVS_OsHbEVZ(9tV}^Y9=E zC{U@jkHD~itWX2}75EPpE!?X9_a|W2T<{Y?um$YWI_yW=kAz_G_Yi?_YApEgfFRWW z4JV-IpdIX{DmP*ry70p>#oXm05&^fKM-aJgE?+L%;oDvCU39tNs7fKr_wgOL4M;-h z#`z!yKQ8z$2g&zl4RXl~_E}=0pg{rOa8K3;DXc!Rkk$cp>;Uo2$j}WeUS`$2%rLS55vPVh{Y7XI4dl7{1lyD&_MGL|Mn8QkCgbOBk%-c>T|Rf5Sif z!16_lX3|RK&4M{scZ`#ikRZZKp>+s^p8fgH1eSRkZYar(eyujaQ0d#LL|cPs#Kr^g z=;(tAnRGf=o!9Nk)U@~9w9JybGG9qS{lI}Q5L(>+i2D+JlHR>gT>6_ str: + try: + from src.subscriptions import db as sub_db + + if sub_db.has_active_subscription(user_id): + return "/compte/admin" + except Exception: + pass + return "/compte/abonnement" + + MIN_PASSWORD_LENGTH = 8 # Hash bidon pré-calculé pour uniformiser le timing login @@ -93,7 +105,7 @@ def verify_email(): def login(): email = (request.form.get("email") or "").strip().lower() password = request.form.get("password") or "" - next_url = safe_next(request.form.get("next"), fallback="/compte/admin") + next_url = request.form.get("next") row = db.get_user_by_email(email) if row is None: @@ -109,7 +121,7 @@ def login(): return _redirect_with_error("/connexion", "email_not_verified", email) login_user(User(row), remember=True) - return redirect(next_url) + return redirect(safe_next(next_url, fallback=_post_login_url(row["id"]))) @auth_bp.route("/logout", methods=["POST"]) @@ -259,7 +271,7 @@ def linkedin_login(): @auth_bp.route("/linkedin/callback", methods=["GET"]) def linkedin_callback(): - next_url = safe_next(session.pop("oauth_next", None), fallback="/compte/admin") + oauth_next = session.pop("oauth_next", None) if request.args.get("error"): # L'utilisateur a refusé / annulé l'autorisation côté LinkedIn. return _redirect_with_error("/connexion", "oauth_cancelled") @@ -290,4 +302,4 @@ def linkedin_callback(): "linkedin", subject, email, bool(userinfo.get("email_verified")) ) login_user(user, remember=True) - return redirect(next_url) + return redirect(safe_next(oauth_next, fallback=_post_login_url(user.id))) diff --git a/src/pages/_compte_shell.py b/src/pages/_compte_shell.py index 984949e..dec2cf9 100644 --- a/src/pages/_compte_shell.py +++ b/src/pages/_compte_shell.py @@ -5,18 +5,18 @@ from flask_login import current_user # Définition centralisée des sections de l'espace compte. # Ajouter une section future = ajouter une ligne ici (+ créer sa page). SECTIONS = [ - { - "key": "admin", - "label": "Compte", - "href": "/compte/admin", - "require_subscription": False, - }, { "key": "abonnement", "label": "Abonnement", "href": "/compte/abonnement", "require_subscription": False, }, + { + "key": "admin", + "label": "Compte", + "href": "/compte/admin", + "require_subscription": False, + }, { "key": "archives", "label": "Mes archives", diff --git a/src/pages/compte_abonnement.py b/src/pages/compte_abonnement.py index de047c1..a15bcc0 100644 --- a/src/pages/compte_abonnement.py +++ b/src/pages/compte_abonnement.py @@ -1,5 +1,5 @@ import dash_bootstrap_components as dbc -from dash import dcc, html, register_page +from dash import Input, Output, callback, dcc, html, register_page from flask_login import current_user from src.pages._compte_shell import account_guard, account_shell @@ -16,10 +16,10 @@ register_page( _PLAN_KEYS = ("simple", "soutien") -def _csrf_input(index: str): - return dcc.Input( - type="hidden", id={"type": "csrf-input", "index": index}, name="csrf_token" - ) +def _csrf_input(): + from flask_wtf.csrf import generate_csrf + + return dcc.Input(type="hidden", name="csrf_token", value=generate_csrf()) def _plan_card(meta: dict, trial: int | None, trial_used: bool): @@ -37,24 +37,25 @@ def _plan_card(meta: dict, trial: int | None, trial_used: bool): return dbc.Card( dbc.CardBody( [ - html.H4(meta["label"]), - html.P(f"{meta['prix_ht']} € HT / mois"), - html.P(meta["description"]), + html.H4(meta["label"], className="mb-1"), + html.P(f"{meta['prix_ht']} € HT / mois", className="text-muted mb-3"), + html.P(meta["description"], className="mb-3"), badge, html.Form( method="POST", action="/subscriptions/subscribe", children=[ - _csrf_input(f"subscribe-{meta['key']}"), + _csrf_input(), dcc.Input(type="hidden", name="plan", value=meta["key"]), html.Button( "S'abonner", type="submit", className="btn btn-primary" ), ], ), - ] + ], + className="p-4", ), - className="mb-3", + className="h-100", ) @@ -64,22 +65,45 @@ def _plan_cards(trial_used=False, trial_for=plans.trial_days): meta = plans.plan_meta(key) if meta: cards.append(_plan_card(meta, trial_for(key), trial_used)) - return dbc.Row([dbc.Col(c, md=6) for c in cards]) + return dbc.Row([dbc.Col(c, md=6) for c in cards], className="g-4 mb-5") def _explainer(): - return html.Div( + col_left = dbc.Col( [ - html.H4("À quoi servent les abonnements"), + html.H4("Ce que les abonnements financent"), html.Ul( [ html.Li("Abonnement Frisbii (solution de paiement) : 50 €"), html.Li("Serveur Scaleway : 40 €"), html.Li("Espace de coworking : 250 €"), - html.Li("Salaire médian : 3 840 €"), + html.Li( + [ + "Salaire médian (", + html.Span( + "coût employeur", + id="salaire-modal-trigger", + style={ + "cursor": "pointer", + "textDecoration": "underline", + "color": "var(--bs-link-color)", + }, + ), + ") : 3 840 €", + ] + ), ] ), - html.H4("Ce que les abonnements de soutien permettraient"), + ], + md=6, + style={ + "borderRight": "1px solid var(--bs-border-color)", + "paddingRight": "2rem", + }, + ) + col_right = dbc.Col( + [ + html.H4("Ce que les abonnements de soutien permettent"), html.Ul( [ html.Li( @@ -88,14 +112,17 @@ def _explainer(): "cette non-publication." ), html.Li( - "Coordination des bonnes volontés souhaitant militer pour une " - "législation plus exigeante sur la transparence de la commande " + "Fédération des bonnes volontés souhaitant militer pour une " + "législation plus ambitieuse sur la transparence de la commande " "publique." ), ] ), - ] + ], + md=6, + style={"paddingLeft": "2rem"}, ) + return dbc.Row([col_left, col_right], className="align-items-start pt-2") def _active_view(row): @@ -120,7 +147,7 @@ def _active_view(row): method="POST", action="/subscriptions/cancel", children=[ - _csrf_input("cancel"), + _csrf_input(), html.Button( "Résilier", type="submit", className="btn btn-outline-danger" ), @@ -150,6 +177,26 @@ def _feedback(query): return out +_salaire_modal = dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("Salaire médian — coût employeur")), + dbc.ModalBody(html.Img(src="/assets/salaire.png", style={"width": "100%"})), + ], + id="salaire-modal", + size="lg", + is_open=False, +) + + +@callback( + Output("salaire-modal", "is_open"), + Input("salaire-modal-trigger", "n_clicks"), + prevent_initial_call=True, +) +def _open_salaire_modal(_): + return True + + def layout(**query): guard = account_guard("/compte/abonnement", require_subscription=False) if guard is not None: @@ -166,10 +213,11 @@ def layout(**query): db.has_used_trial(current_user.id) if current_user.is_authenticated else False ) - body = [html.H2("Abonnement"), *_feedback(query)] + body = [html.H2("Abonnement", className="mb-4"), *_feedback(query)] if has_access and row is not None: body.append(_active_view(row)) else: - body.extend([_plan_cards(trial_used=trial_used), html.Hr(), _explainer()]) + body.extend([_plan_cards(trial_used=trial_used), _explainer()]) + body.append(_salaire_modal) return account_shell("abonnement", html.Div(body)) diff --git a/src/subscriptions/client.py b/src/subscriptions/client.py index 4ce2ad2..6e2b8a0 100644 --- a/src/subscriptions/client.py +++ b/src/subscriptions/client.py @@ -5,7 +5,7 @@ import httpx # NB : base URL et schémas d'endpoints à confirmer dans la doc Frisbii lors de la # première intégration en environnement de test. Frisbii Billing/Pay s'appuie sur # l'API Reepay (api.reepay.com) ; ajuster FRISBII_API_BASE_URL si besoin. -_DEFAULT_BASE_URL = "https://api.reepay.com" +_DEFAULT_BASE_URL = "https://api.frisbii.com" _TIMEOUT = 15.0 @@ -53,19 +53,16 @@ def create_subscription_session( cancel_url: str, no_trial: bool = False, ) -> str: - prepare = {"plan": plan_handle, "customer": customer_handle} + body: dict = { + "plan": plan_handle, + "customer": customer_handle, + "signup_method": "link", + "generate_handle": True, + } if no_trial: - prepare["no_trial"] = True - data = _call( - "POST", - "/v1/session/subscription", - json={ - "accept_url": accept_url, - "cancel_url": cancel_url, - "prepare_subscription": prepare, - }, - ) - return data["url"] + body["no_trial"] = True + data = _call("POST", "/v1/subscription", json=body) + return data["hosted_page_links"]["payment_info"] def cancel_subscription(subscription_handle: str) -> dict: diff --git a/src/subscriptions/plans.py b/src/subscriptions/plans.py index b0a13c1..7fc3bac 100644 --- a/src/subscriptions/plans.py +++ b/src/subscriptions/plans.py @@ -17,11 +17,11 @@ PLANS = { "env": "FRISBII_PLAN_SIMPLE", "label": "Abonnement simple", "prix_ht": 20, - "description": "Accès aux fonctionnalités premium de decp.info.", + "description": "Accès à des fonctionnalités supplémentaires de decp.info.", }, "soutien": { "env": "FRISBII_PLAN_SOUTIEN", - "label": "Abonnement de soutien", + "label": "Abonnement de soutien ✊", "prix_ht": 50, "description": "Mêmes fonctionnalités, contribution renforcée au projet.", }, @@ -74,6 +74,9 @@ def trial_days(key: str) -> int | None: except client.FrisbiiError: logger.warning("Impossible de lire le plan Frisbii %s", handle) return cached[1] if cached else None + # L'API Frisbii/Reepay renvoie une liste de versions ; on prend la dernière (la plus récente). + if isinstance(plan, list): + plan = plan[-1] if plan else {} days = _parse_trial_interval(plan.get("trial_interval")) _trial_cache[handle] = (now, days) return days diff --git a/tests/subscriptions/test_client.py b/tests/subscriptions/test_client.py index 18e52c2..011e727 100644 --- a/tests/subscriptions/test_client.py +++ b/tests/subscriptions/test_client.py @@ -29,29 +29,42 @@ def test_get_or_create_customer_creates_on_404(fake_httpx): def test_create_subscription_session_returns_url(fake_httpx): fake_httpx["queue"].append( - fake_httpx["Response"](200, {"id": "cs_1", "url": "https://pay.test/cs_1"}) + fake_httpx["Response"]( + 200, + { + "hosted_page_links": { + "payment_info": "https://checkout.reepay.com/#/sub-1" + } + }, + ) ) url = client.create_subscription_session( "plan_simple", "decpinfo-1", "https://app/ok", "https://app/ko" ) - assert url == "https://pay.test/cs_1" + assert url == "https://checkout.reepay.com/#/sub-1" body = fake_httpx["calls"][0]["json"] - assert body["prepare_subscription"] == { - "plan": "plan_simple", - "customer": "decpinfo-1", - } - assert body["accept_url"] == "https://app/ok" - assert body["cancel_url"] == "https://app/ko" + assert body["plan"] == "plan_simple" + assert body["customer"] == "decpinfo-1" + assert body["signup_method"] == "link" + assert "prepare_subscription" not in body + assert "accept_url" not in body def test_create_subscription_session_no_trial(fake_httpx): fake_httpx["queue"].append( - fake_httpx["Response"](200, {"url": "https://pay.test/cs_2"}) + fake_httpx["Response"]( + 200, + { + "hosted_page_links": { + "payment_info": "https://checkout.reepay.com/#/sub-2" + } + }, + ) ) client.create_subscription_session( "plan_simple", "decpinfo-1", "https://app/ok", "https://app/ko", no_trial=True ) - assert fake_httpx["calls"][0]["json"]["prepare_subscription"]["no_trial"] is True + assert fake_httpx["calls"][0]["json"]["no_trial"] is True def test_http_error_raises_frisbii_error(fake_httpx): diff --git a/tests/users.test.sqlite b/tests/users.test.sqlite index 16de7617d850c7ffc948e96a5acfc449d610c873..6370d61770f88526c3495620a8a53670c2ed1d3f 100644 GIT binary patch delta 676 zcmZozz}&Ead4jYcD+2=qFA&23&qN(#RaOSQ-e0_2Y79KA1q^&;Jj-~`aoh7mbNu0N zVQ=FW;o8qyz-7E~;v1Iw7GXYiadmaZ_SBNZq@2{^(xl?#qRfJl%>2A!6b`de zt3rsQlaH$cij;x|mjV!!7N-`)XQqId7{Jpn#MRw3NFgA|)7LR5Qo-9bQU|1|ULmb0 zvp6X;Gd{VrxFkOpXhKF}UP?|X*n|+*h!C(cxGH3OVJZ=-3UU(jFsz0OAXF8XB$kvG zLxiKzl_QiTmlhSJ=9R=3q!wl7r^Kh`A^8lUs-!40F()4AbtGA+nUkYA6upv*QWHy3 zQ{od#&}~vs0GXoT=MTi8K0XkCmKLPoQa-tcH)wMk`(mbgM07WD^0SLOsxvm4XXd4( zR%E7B#3P4LF+5aJL|LIx>FMX<8liw&6F64={ZJ!YBTXE8jA?G(&*dn(C_rG5fxx1K If<*xe0FFAo5&!@I delta 137 zcmZozz}&Ead4jYc3j+fKFA%c=F%uATP1G?~Wns|k{l&|r#=yni#=uv`vyAr~w>?iZ z#~=O{_BL)2uKoO;{GOWy1(?`3x3Mo~Vw^c~;?&Lixg13|D+oN`pSVC_k%52!&@d2S Z1Y%|&W&)~z&%gP