From 961bac2b0e1b33da8e11a9602923ae1d4ab2df66 Mon Sep 17 00:00:00 2001 From: Igor Barcik Date: Fri, 27 Oct 2023 14:24:55 +0200 Subject: [PATCH] Updated with LuPa2 changes --- .env.development | 2 + .env.production | 2 + .eslintrc.cjs | 16 ++ .prettierrc.cjs | 12 ++ Jenkinsfile | 24 +++ bun.lockb | Bin 0 -> 122779 bytes daisyui.config.js | 31 +++ frontend.code-workspace | 10 + index.html | 12 ++ package.json | 46 +++++ postcss.config.js | 6 + public/icon.png | Bin 0 -> 57346 bytes src/api/AxiosService.ts | 98 +++++++++ src/components/Breadcrumbs.tsx | 72 +++++++ src/components/Card.tsx | 35 ++++ src/components/CardGrid.tsx | 13 ++ .../ConfirmationDialog/ConfirmationDialog.tsx | 39 ++++ .../ConfirmationDialogProvider.tsx | 52 +++++ .../useConfirmationDialog.ts | 23 +++ src/components/Loader.tsx | 17 ++ src/components/Modal.tsx | 109 ++++++++++ src/components/NavigationTree.tsx | 79 ++++++++ src/components/Notification/Notification.tsx | 136 +++++++++++++ .../Notification/NotificationProvider.tsx | 37 ++++ .../Notification/NotificationStack.tsx | 25 +++ .../Notification/NotificationType.ts | 9 + src/components/Notification/README.md | 2 + .../Notification/useNotification.ts | 34 ++++ src/configure.tsx | 191 ++++++++++++++++++ src/contexts/ConfirmationDialogContext.tsx | 19 ++ src/features/About/AboutPage.tsx | 32 +++ src/features/App/App.tsx | 108 ++++++++++ src/features/Debugger.tsx | 31 +++ src/features/Home/HomePage.tsx | 5 + src/features/Login/LoginPage.tsx | 102 ++++++++++ src/main.css | 3 + src/main.tsx | 21 ++ src/routes.tsx | 69 +++++++ src/utils/ObjectUtils.ts | 9 + src/utils/Redirect.tsx | 18 ++ src/utils/RoutingTableUtils.ts | 79 ++++++++ src/utils/StringTransformationUtils.ts | 22 ++ src/vite-env.d.ts | 1 + tailwind.config.js | 11 + tsconfig.json | 49 +++++ vite.config.ts | 15 ++ 46 files changed, 1726 insertions(+) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 .eslintrc.cjs create mode 100644 .prettierrc.cjs create mode 100644 Jenkinsfile create mode 100755 bun.lockb create mode 100644 daisyui.config.js create mode 100644 frontend.code-workspace create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100755 public/icon.png create mode 100644 src/api/AxiosService.ts create mode 100644 src/components/Breadcrumbs.tsx create mode 100644 src/components/Card.tsx create mode 100644 src/components/CardGrid.tsx create mode 100644 src/components/ConfirmationDialog/ConfirmationDialog.tsx create mode 100644 src/components/ConfirmationDialog/ConfirmationDialogProvider.tsx create mode 100644 src/components/ConfirmationDialog/useConfirmationDialog.ts create mode 100644 src/components/Loader.tsx create mode 100644 src/components/Modal.tsx create mode 100644 src/components/NavigationTree.tsx create mode 100644 src/components/Notification/Notification.tsx create mode 100644 src/components/Notification/NotificationProvider.tsx create mode 100644 src/components/Notification/NotificationStack.tsx create mode 100644 src/components/Notification/NotificationType.ts create mode 100644 src/components/Notification/README.md create mode 100644 src/components/Notification/useNotification.ts create mode 100644 src/configure.tsx create mode 100644 src/contexts/ConfirmationDialogContext.tsx create mode 100644 src/features/About/AboutPage.tsx create mode 100644 src/features/App/App.tsx create mode 100644 src/features/Debugger.tsx create mode 100644 src/features/Home/HomePage.tsx create mode 100644 src/features/Login/LoginPage.tsx create mode 100644 src/main.css create mode 100644 src/main.tsx create mode 100644 src/routes.tsx create mode 100644 src/utils/ObjectUtils.ts create mode 100644 src/utils/Redirect.tsx create mode 100644 src/utils/RoutingTableUtils.ts create mode 100644 src/utils/StringTransformationUtils.ts create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..55315b6 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_APP_NAME=App Development +VITE_API_URL=http://localhost:7082 \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..b61c784 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +VITE_APP_NAME=App Production +VITE_API_URL=http://192.168.179.36:7082 \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..d29b926 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,16 @@ +module.exports = { + env: { browser: true, es2020: true, node: true }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "prettier", + ], + parser: "@typescript-eslint/parser", + parserOptions: { ecmaVersion: "latest", sourceType: "module" }, + plugins: ["react-refresh", "prettier"], + rules: { + "react-refresh/only-export-components": "warn", + "prettier/prettier": "error", + }, +}; diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..eaf4b83 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + printWidth: 80, + trailingComma: "all", + singleQuote: false, + semi: true, + arrowParens: "always", + jsxSingleQuote: false, + bracketSameLine: false, + endOfLine: "lf", + useTabs: true, + tabWidth: 1, +}; diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..a954736 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,24 @@ +pipeline { + agent any + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + stage('Hello World') { + steps { + dir('frontend') { + sh 'echo "Hello World!"' + } + } + } + } + post { + always { + // Cleanup the workspace + cleanWs() + } + } +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..071809320825f1fa170604d9734c9df9e4d7e260 GIT binary patch literal 122779 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 z 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 literal 0 HcmV?d00001 diff --git a/daisyui.config.js b/daisyui.config.js new file mode 100644 index 0000000..eb83cfb --- /dev/null +++ b/daisyui.config.js @@ -0,0 +1,31 @@ +export const themes = [ + "light", + "dark", + "cupcake", + "bumblebee", + "emerald", + "corporate", + "synthwave", + "retro", + "cyberpunk", + "valentine", + "halloween", + "garden", + "forest", + "aqua", + "lofi", + "pastel", + "fantasy", + "wireframe", + "black", + "luxury", + "dracula", + "cmyk", + "autumn", + "business", + "acid", + "lemonade", + "night", + "coffee", + "winter", +]; \ No newline at end of file diff --git a/frontend.code-workspace b/frontend.code-workspace new file mode 100644 index 0000000..7ee208b --- /dev/null +++ b/frontend.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "typescript.tsdk": "node_modules/typescript/lib" + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..3a49d4f --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..a46f3fe --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "app", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "tsc && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview --host" + }, + "dependencies": { + "@hookform/resolvers": "^3.3.2", + "@radix-ui/react-icons": "^1.3.0", + "@types/react-router-dom": "^5.3.3", + "axios": "^1.5.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.47.0", + "react-router-dom": "^6.17.0", + "uuid": "^9.0.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.10", + "@types/node": "^20.8.6", + "@types/react": "^18.2.28", + "@types/react-dom": "^18.2.13", + "@types/uuid": "^9.0.5", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "@vitejs/plugin-react": "^4.1.0", + "autoprefixer": "^10.4.16", + "daisyui": "^3.9.3", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "postcss": "^8.4.31", + "prettier": "^3.0.3", + "tailwindcss": "^3.3.3", + "typescript": "^5.2.2", + "vite": "^4.4.11" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..7b75c83 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/icon.png b/public/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..dcac0cc21a666eb27aad2465003f130b1c8eb5aa GIT binary patch literal 57346 zcmeEu_dk{Y8~?HQR*39b#~vZuv9ecDW=3X0Dsm8!y*ol7E0mp)EpKs1iX$Rak}}K4 z=6l_J9^XIU`}^19-Md1!`+mJ%*L6Lw=ks~pab_lZbkwJ)Q79Chfj-6pg(8kdeo>Oc zXIjKE2=E)#4Sm}{6pH>oL@$&TTN15}l)PJu{U2~_2r4u`O zRUm?l=_;QWMw8UoOr?y7;B-~Ow2e0B`+)S()kHD9K%-hRrW0sNQ5#KG!$Qs8^bDJ; zn_MZ3AK9#3%jdZDX>3Jyb~tFLp?#=BcI|Lx7(1In#7o8mAJ+2qP2oiR z@Ue)crH+e*4^2Mg3(}&3$Y-Vg|8M_KOHg>mIY$^hNF3qofRm*qiE-YXSkuK2*x(|C z6{&($BXVS@Eg1u5loLuHLrNZvvC`QrcrRzXr4XgX&yW1QM35I9{v@{SGVDNM1#h-y zu-LrlAJ_t!&S2kbEicn=QHa>bR6NL%V-PG~B}Otnx^t$KNRIoY24 zrWoTpHC1`mb%>A2ngqEnlhNqs)gdWb`N%g?v?o}JHH0%f18M2{Mj6c;oXp??djoukIzp9>*0VaD7M|RW@yzk?@v)i}$iR2iq1j`I zM&;53<4)OYW32G(tjy**`lGDQ?5{UeP-aAhM|)-FMZdd97@XcDa_up&oQ zB`})zrj{~i>ovRLnIDI)g|0S5BshCbk>zeVZ*?>os=921nNCZ-zcOws7Bh zVk-P3lX;U1tmUDW9`1&>m)HA=3CiIOsT*H3Uf8#u6wpgXmvbb}vE$tMXtj$yD{wjL zM>{TwoagA*+BWE6MHYg8V|g?-0%Su$>tc!M8SQh z;PS#8B0AY90tbwCvr&W6;Zcmf<@8=?=1x1;G&4u(j1Z%e{2)0}Ox@gx2q8*y&tjzGu8leIE*fh%Rn8%7V?n2!%E4Bva?2>+e(Q!n<6hwIys zJqRf^3!^x^3#o6?TLFLV{8#_o~zNTm+BrwuOOZ*1R@-9*a%ww)2Gt4!l=?3X#!kO9(s;t#*>+zSb!p6EH z3mT?*%7C1f9fg9kr?~QR^!J2IigSwUU$br=6zt|;-Itc@_;)Ih4Opba8W54?~!m@X*3wwe*w|=z6-6&#%V9 z4$)l8$13FHWi%PrwP>he3D7zRy~EX5{R2IK+8whwVmZzqr@{3i+4XIeH8fPZFim9 zpw&r>iAK7?I)a@xf8kL)TTm`ywv!Sx&bf=1O2_^fNB9&AX59GReQ4Jz> za*t7F>H@}=KJeIh!_&xlWn!kCY^3XvYW$>Z z65gp+9P(?V}hf3OS7CDe3gi zeA=Z#-l80@pK3q2+7mcJ;>Yqu7wl#HFvjbBDey*AKr58n>q?DA_G)_+s8s)zCdMtr@A+BV{Ja z>NKSwY?_xSl6Qw#US3{k5D$;dggiF$3Ypso-HSCT{wMAJTt!t?^q1`X$t0S;5RZ+W zowcmHAEIC$=5FdrrK6UAsr3na%su2TGK7s^dsnxAeA<~GTPBHQ9{a)~%`^i2g>4BG zEw>8$_VjI@H_4};zj@ogpkq!t9@Mm}cKYy@y|EX+8+r&f@5bf_y{7ZeYT9)<_zP#= z_^!s58NO^1h?`TKLX(knN%wI@sUQ9_WtVkNq^8_@N~l`Gky_2qHa)xVOgX!4i0dSB z`rPc_{HruKWO>~>EEY6Myuu%PjY^Z|qP2)hJzode+xhpNX#QE4V3j(NH$zW7^_)tMmIT?nywG)nhSm#=q-D>a zYr_Y_Ar6Dd|98Te(@&7(kO)pSGR5%KeL_RQakHiDVbqSH?Jm?wka8QyOmB}l&wO*M zG+8eGzH{s_b~W(M@!>X;4ii_Ox&24v2|JSIqcH>_!JmKr)rBVpucfT~Qcj;gX{xp> zSZb6V`7b$BM{9N3Tpl~0f2Ea76V>N$Rr=na>p%Y&F%B$DTR~9K%LX9;XSdVvt|-(e zpNrxQ!#VOa$R+-Mx|W}AGVP-f@c!Yb^282c82}ke87~?FGwA1wIA-s zDi^mG=(j|V%IUD`NEx$eGV*5NzpM{R_Jtpghx29Nd1Y3NFb|WEb>)M# zSoy)np%#3l(-s%aA?yC->jR#PM9#q{jfk}hwp-&{(>pHJJF~MZ-rz;D?h~kAtv$^g zjLhbaNUc!DxHC*PR@OO;I62$mLS}at@>7t!3JC$uLR1CYi<=#5&sX-p`!%9z1auR- zBFSh7kMge8#@b0dQ@iBF6c-!9$!Q#0)+4yY)+bHdqoI(iQ)^?@a34y+xp(K+R2qun zW`d{E<`<5PAbkZV8KeVLvcW|kZqM>SWZTrc8ygHgB~4y75iabAfXo;ayVMd}NEzc? zR{2?wax!pOwf6e7GE(~+eV>;yQ_6WubK3??lsMX z&{Vho&B^g(*D>!Mvtl+s?$u@CUzDQyyJ<3s&k&g_&At9Ql~3DBL5fyeKIBc~itj1o zGik0ATl@MB=Zsk(z948QxNe{v!ViN^I1Pq7RNIP^1idzxLr$Hw6OwH)DqS6oJ;R-bAvh!F8bq-Q6MVAAof0B0{&J0#sB zp{Q`OU8Gj(ONCW~WJdhHw!lo;O?9|Oe%vk!_P$Y&J+V+KEyl*qgDHVg_d|%Mu5q=T% zFMTk^JgpFTiKc;j9h7l%!y-TuL~nD?`RQV@*xr=fU)1ULbUg@2+)V-uJsf@{TWazoK1KbA zz0p@7p1&bclI{#GZCqLFpX|qF)D1ZKO)L+sE9NHy>BVu2LkXfDsKJyjddiu zZ_eucrR$y-IgfAtoZ>`Vu&mEijGL{{>FEdDA)?4qZK-*hFpa%$H^#K5Bne}zODkg- zQGoMNQrF&by|~;!4ItMNZ*scscC~}IX#L-clI-vG)R8C%Hh`6I_lJezgOO|d?B{Qx zQD6S_@jx3<@##KXieUL~L0#jTLRAtSOg*L)BwCkc-sY?GWE<%QaW7>*U;koCth=aa z8<{2K7M$x(Z;iICk5^EamD7Y03^4?I8%upFzD?B7hRRk8>CHNx z>ScXmx!W9l{)yAKXHO5S?V~%^A6A#m8&PqI4_X-0hu~6)zW(o`r1v!p?H6+7y;BG0 zou+Q#8GU})eC%|Pd4<6|>Al8WNDW7ntM8s?4&YUkRnznLO^$}6teT8Ydz-%?wXz}c zQ-SIQoQ+By%$k-ow)`HTM5-B~y8W5u?4>Fz!#!tq+;Sy)da#a@n#uf9_TOq#%!vp{=qI!oW?Xr$@&&F%@>-c4~;^*3}qemvwdgWTCIF5GqL)SO0W!q_P%cfqlTg?b8~VCk9Z5_!%Z;+4k({z zViV&&kdT&Z8>W*Qq&rR2INLOMouF6x&bZ}Ir7y@SI;+iKQg+v%OXA8`{NPaTxw)T3 zDQ_Sll`d)tby0M6f5Z^nnz=Dnny$g-b@!pAq#qYFkn$W6Y}@&kfSe@S9=b4YttFlzV}(s02;G@ z$P-HbqR6JQOrL*hsV82_Yj}eoddgFaUFD7xcj#};#8dLrfcio^*LhPk9cQyj#onpy z`=A}#nq3*Z+JBLHWQ&UhJ^d`45~~~uaiY#Y7(Xu`R%b-3LH6Lm>09hL3Hx~DjK?^? zoJ_1~$EKG1r4>}0iGJCcOX-sxRw)`FE4JkF5F-+u#ia&4#SplmZQK&5Q?Ek*QBICB z5-z$WCQVBMY(S#yyDm#p)#&M4Q*<%THxgb_^Kx`USC23P6MmhCjD58gs+p?+1vj%; zBo0~3^?(;4r2L%I+kBhqV0dTD`qEd<*`1in4w(fJ*{_dGTe|JXdy7)k;zv!+(?ROH z$dg6uyvxqdUzm)uy>csu_6Knm+EkPFss((2^8ZD82OM z1gw@d$OpOfyg~4-ng&$b9yf=b_}xpKG50m=$#l(5}_g+F4Bj~+s zYO3j=T@L0h6xHJD+i42lKll00TvAzewdAK#dJo&n?)J9FB6NblfyDEV*|F*{m73-! zKg$n0)qB-^2$%VT<(G*FzZV14tG(F2F_wdNEzq(;4H2XY+brVw3az{z7TZpJAh@%E z%_njuOG}6W9_1$=usrYxcm7}*tnmZ=Ck1lU{h>;S|8eUu zkuz{7=K6JTKwS~@lqeV|$)fv7r`g{Xi!xNG8zumY*r9LTi}g`R z;H9*>!PZyn#h4b+)#!_~%TOdm=@q}AnyV6|Xl>bSbT-|Y(@Mo+=ugLYM^U&;w*=Lo zX?8;$PV)d>9I6%M_o9i?D&!uSS+5~PC?%5=KqJ^)un?^HEmq+71N8%3L+_xIq%Q) zymIjS{JD-lbSi%*Txxtd0ZwUbte%ufNj~Ga`-k|Yosa(b?!!wCah{{4t?d zUZJyk+S(+^D(m`fyFYR(ex%;xVQP2);l;ymOf=%Fa_6_xKYXShrh3|I^JmmylF2rDPDf$&s__!dqf$=;4G(s5-DIvfPQNxL)CKaa`qI?c$=A9p z$STL#GOS9p{nq*%x-rBq<)nd6e7&9>=$R8hZvRl!Pfm>CyU{4d(vk&Qj1f5<_84~6 z5V>!PZyx?6@yT;ta>hFy!(TsQ zih-zIY<>#u&E`!yV7t8xP+vnq_XmIXAS++{F()Ff;#2^#2}YC zgzdT^OKp%|@bsx>(DSLrq^q?PgPlrDTV!Zm{zX0+cU_qX3z|Fcloq~}Kg+WqdV2HW z8~;Q|VC*s7843-CP6MGpK z%_ixPm>|(Y`O3~joMWk{tVinp+P>E|b7AA7QdF2jMM_2&|ZFZ7Mn zCmRR(M2BBn^#{mOk z|FD6_dtUkbEeC<87VQW9h7K=orc3is%a)ek0CZZ zTC`+ zH~S=Nig0X;Bb(iJo@yM}Oag`uXb)Bs#jm9D=S&!c-IP*@_=3(IPLSou#@8jjMSiwB zb)52xc6#I8=C`rAfJ?7;?7A;E&n0`2CaHBzO3^~k8LF0IB-|r(b@A5TM79*|pGrS0 z4MO0d2ftMbWBw~h{W+R?O6&0ZnIiRXinv|1eW$~AdU(EVj+*L0N6+D{#Jxq%%3tANrzSk2Uzz`Y`;3Ayi)Ri$P$&7rBp&p8 z5U*SZKhAZEDZLtatkpjHsH}bI7h3p_p|6sYh{+2 zz8DD;@Op)&ipfPdtnbwIhBp@)Ne5kJfM364A9Ui!yC=_Y35yLkzf>4Z{aNo({k9-f zkEKWI_rTfeFPWAm-B3WwC9L4Y&yLoiA2tX>k~6lBQ)7yGdf6hw=cy~js9ZEeIQ>`T z5JIAuixo&V6B+%8yEnMR7&kdGBVBm=dDBVW7-uL8pcBycbOGagWA)VnKV@Y4>01MT zu13d_LhV)KD^dc!Z7t(fq{iT1X9}KBDTk0>LR_~doGE+E#PPpGf~W-JlrKa!0+MAE zHUMVJFh@V)2fC5A?5LX|ETVo`jT2HLba_cy=}seZ#$#l|Xg($?2_BZ9h{U8_kc|>c z46`aq+^Klo4iX)fghZT_egz2g1%3SMWz{2vNf&%1?LrPrLgk|KJ=|qmfPepB#U>R# zuRp!ocd7<$C@$t0JpT~s*sXYfA|lbHdUBoJxGQTm!h0_S&zGHhs4oRX%?x(ip6f;z zkxhF@!>P&qGB&uWsFcZkfkg(ecp~TKBa{!t0M#QENJN>(h?~CD$amb+e;Ys9Q1{irOmm{ zwzhjNp0SiprK$QKvPK|!8m~{sBcNjGV`a~Ge8$*F?n1EFZe`nV$6>jF{ouNiWDma` zIh6d3%gOaxPa~pf*shKg8$hcIl$47!?9K}NplnpgkJLU9O1M!Aj_)JdNy1cQa z8_7KB+5Dn4*8u2XoEHh@Kz!vbJcV5Fm4KP{goOdTs>iD1t01YmMX&^VUs!GD3khnd z$YVrq{-_s2@O;Z^p)=}T2l>j_m@Wbr!-(gKagN3Q{X`Ldxa25}-P=qLj?OkJEmlY< zJC8BHzqc2%(@~8}PUz*Qj*h&{-SKzQTRLdb>R`$7m>5O)`0@2NcFaf%`bN0&Sn6eQ zJoSaf8^6DFBc%^n8(Iz-j4zw;#^-^01t}gx$MC3k{Q=o_vaKPJVV!27>3o4Rp86fkev;&9jENewAJQ&)-gFiq7NH$Y78?kn=rKQvTmC zZNFdBARROCI&1qvpH7zZ5c;ARgK)HZ43Qdcm7J$e5p?2~b<<15ehlZQ1aJI!Y9?Ct z#1NI~<1K<5pG#jqC7*%fWVzpcAr+cTK#f3vrVDmwU(1UV$q!{dzg(k@UlsBZe7fom z)$=b=mmUxHJ`o9+E8rVJl7;>&s@(F~x2(_`Sl~!z)T++~*w))v@2vXge#oo5N zJstE&!8d2%zS;qC&e1eCJuJ4F&YIy$c=uI%%o4KeeRA?iR( zT`f)BLw{;CxgMh1TZa}jAnuWZ#fBr&O~I2m_WY8|QZ%Q)h!->E2{>cj5*yZ1cS~U7{#yH>L<_pSQpKCA^1>75SZ@o|bhzVqR z6^MiXsbBHEg#&^EIyBzoab=P~x?|G`Op8x)*Be1xQSf2^_`){Iyhyj47+XFqLtEtf zi``>Y@duaIqDjCXzP=?;Qe$2PF5n3N>6()cZ&(d^1G)t0i111*Kv!XtA7i$f?)`X? zIH&kX&Xd^zyJd~=Nx<{r+(21xc!7@IcV@4c=4EHJxV@pm0AKLT`4&kd(e?RNAi(b; z37<_G)@%y&Y88yl>GHVc*@$VWN%V< zQlw~|5krcEC)#<4HwZ`S9`eH^pT~p!jlw<~aKm!OwV}HKZ=amMgq2h(-I@cVO!eHl zbkNy$HM8o<^a2xg=Tgsj(Rc#Y_%@a+_s6v%=vvOOJmF-QPa%=Zh;NR=2o_9SPpRm=H{yB zxL+O6V7YWhlEoI5xgLQHY3i}htMgPrIG?+seT8VEsm4k#t9w88tcN$ibc7VOgVo!e zB=6Hb30b(o2i2f+x^MBy8~;DhIzz!(TW1&;16T@VQYT*S2rFAphbL5b3JA@hc@6Hg9?XT%W>_ z({sf_>SsZ^$_Si>MGO&1$>}$HMp_ad%&+}whzcMoqzH0m|CXl5-3^Bd(Lozz<9w%t z*ir|mKQt0$G|k_wznIXH zzHCdY;Z|C5-8#8G|M+0xks4N6!FLi)KVytDo-FzO5=~Q`HM58kZ9JD; zo{pdEtmI_c!UE=$5Sacioh~$W!pX1yv6y0jomyO64B3?| zxF9lQEaM0ua;GCj&KSTTA)tmpsdlt8hdhPa!EfyoJJx{$akNQU7L12}S(8o$!B_CS zl&F_CodZKz&1d@xQd=^8W>1h30d`mj1_fzq`G?f4WgCRo-8ooLPXPr>(Zt%-vTR#_ zBi130x1nObbw*A>;Ts&dE+C7ZMgI4Z+&pXe-3!+M^3zn7tWzgu4)s8`f@tSZ{Y{c% znhwYs=k~O=mI<6eVAD}U1SjyA#Sje!uu_;=-cA=5E)ZhRw7t*F+Ofadiu6AYp?`YQ zODqZwb)R`rQM-Zg1vG{H+bfZ3*?-C;IWF~JwXLXZ4h`*vr){jDK_p^>g-H0SkQDAWg2~p0KaFHps7Gk zTD57qe)?an5gPoam|USb(t0jVE#ad($zsz#BGMdvl8KZEa1aQ`P@oac6OpM`nr3wY zg#kjRCWl1RZ5rHkw;`x=iQ`bt*!mWnAv=IJcfQ=CsDZQx{0)$G^<~2xzAL&Ito8t> z-|1HzF}2gVZnou~Immwt!D10Nq3ZvF1dvhcnWJl-8Gp6*(HsBA%s~$Y4I1wnj>`G^ z_!y1K8E@={=9GHwfiU=NcJE^oL3$N@UVkJc#RnI+`W*dceWpx^ij(JxQ6qbMd#oBQ zKou3={_L-Js?R@1&;b4)GH!pnq|~z%H3F`Trwb`FNBeMo&J~}G7}OvTS(p+&8hh2d z{(>%1@(uF5no;EC^e&$aFUnB<66rnOt75|B>r9$7R+)(wl$PqcQO$DpzP8_-VM;nCX)8aszQXJ@ZUqgLH64;8L)&rdbm9IrLnLHDhyG zO+Pfu9Cam+*&{ZgU{!@AKJzr>742?r`~1?=QrZ3Oqx7|}ipkU}s>9wJ*Z#cW^ivI= z(?4Y~d8Ol_G&i8T;fF%!dZZAG(;Vck*AOTd$fr+IlfC0BzRsp&jz4)5bmP)kK?5MZ zkr1^5jscaED-7HRh0o;6a#xBxuhLnDQqRKDQgZ0}RNono{`ajn0Z}f;xP;fT%;msY z0Z0-B%?7}VPUnN2KM2JCXXO;Cry0~cW;9=0-0IJ4I`Yr(x!~*TYrv!yu?bCS$f+1- zgTIi0ttT8sSzFYNG1jIysrhD_NM3Ni9YWC?Wie6I6t_GTW1e2QxYBpg@l`NHL;_JU zaK6)yU{^B8hu%akc`m&#m>pcXrDr&EOFh9Z`%y00W$W0c(Z}}kl`GFzy`h6R)^ZH` zl5}n+Wc!US%c9h$DVrRff3B8ZM6<|0_%0GVXR~qu>!30mwbX(#OYi1eq)!*i?EX1& zusPm&bOJ1E{CVL=!9j_Ol1etOvJ;aHMy;mLIkWR;4SogksSB)@_YtVL-voK$4GT)> z69#HxUv18sV#uZkD7Z*L^@?o<*2+PUX^YNIt(0EYm<8Mfl*CAXE=8+y@VjT{HCqP? z<&~FWEvPbMhNL^|4`BeCK`C%6Gd;N&fneDZ;$yQNA zb&loAwcp?xLAW?OpzDFBH@BattF+%8Uuu2SpDc}F&4hosAL!~Sw56<+$Rej=TTW(j zViGVgQ0OK=%KHN>oD;Yc7hS&ua|gV#!^id1q3KIYxxw2F3GJUnxp{NM(0{~0q&$J; zu!K-s;u){?)a(zDvz~rZzAF`Ye}C8KAJR5`4oILrIWH}~okd&yaa$huLB4LjWFh#> z&sMO^NnQV}_)sS^1s_k$v?bwAZ6MaUE-7NV{IBtlba){X8Ni&%5{;h1dO^;`T>|6D7aRd7bKE8i5o-ttS>C|gEH z6&DoX*tn%S$g{l{*mK3NJz5||g?X*>xMS~8hZ{H37I$9BbN#zh7w{R)H<=c5CkHc* z5$n$R)n<}cc2%ysddlfrZX&yR>q?lZA_}*8BD+0&*kk~Dz_9#o#+Pr$M{CE(v%vju zfd^!<*(lvLx4@J~-~Ja7A1a8aINyjYwXbPT@r=|4mIn;Xym`StIE?aCY_Yz(zpr!r zoyM*AwYz#iGabt$fQXD+#~E*nk9POA-Bo4GhredG#AoJv4CZ^xDaL@5lL(Hz#Vts% zh;{*9o8-%jX(HFlb@&%ez*MBmtXjW-2&Av~wwC;VK7H^F&iFmp`Fo%*$&_hqyhAgi zaM!kk29X@MS(VZJ9I#FUrZdJ>pwD zm&!kyq+6&+$o^Lxa|43?pWi@r)67~{RD?$9=?%-fi|E50G4;IfTa{OuAZ_T9p`jO{ zBu2O%)ulJr70A# zsIBtq^4yi-)sVagh_{R0#!e7=rcc z-UmIN@@&8P^O&FY_}G?=4*SA32T|4$aGu@HwU>r^XFTc?w4^YT(^Q;Uc?ejm>1J>I ziN&MLzUF1hqG8Tu+D~Kp`L>tg-%zm#7dQGgDN9-$g+CU;L!u3IDKa>barW5iN(^kN z#zxFGo5LNxu!A|Cti<1`XR%K_bIy2c6P}Ei;?(98-vF%~AsN0qhyV|wmzlEvy4JF~ z==Gp6lSRr=(^D8w29Y)%Idb5W2Qf>^xc<98nxn01GEJ0L8=(G?I-q!v>;E+cP!wS| zfeO8eALcJKO_7hbs%s-fAsYNikjBndH4{T-f!xSKjk3Pxn&aN8vw%7MuyMqR-*2fX z`5(m{w(}LapRwL}w9?6h-tJA@+iwx$74QBGf?3J0i{{Ach_< zp1H-PH*{;1L<2wZQlTIBvtGY}Ja)YaIEANXT|l|r7!Z3_0GU&Ba6Nd$JU7f@0bE|M zrO@EhDp!`dX0;e0+47SgA*+En*w>Xg0#Ay&jHgjrU)Z&u@=@!Jq<;`2BZASP0+dgu zx^8JMW}Q7e)K4~1e=fh6ku~{OC#LdLrwRXcdANt1^duP{MxOwY7+*dAY z&j^Ft2|wryM?9nyW|N5)VFx8{ryIxK*YL7oeWwK{ki>NZmhJf;OJ>G_?bLmXJd%ia z=d$~^Q`ZF4IU_D7v7funrEvXoELd1h0uIj|dJQ;y+BCN8Zf$AJ>z^dSTjTeBh`9ZF z10D#7SAWKhA47;$kfdP#Ki+%!YKb{zvV!$5u5Co%m;?hp0G70V*6KU81I8I-mA*u? zIcNZTSgx8e+3htf`0(0NhM@U&hVK-+TBzd1D!Vvf-}R@fL-cO=sdvmzAQ4^b+(Uk1 zVyWRBhS1K=dNH+8SFs-tu6*E(O<3#5#;U^)y&vCQeYNPl#Uc-I_Y89^%0r&!L|^Tx z2Upo+CJpzYcy=M932;cuzG&3x$%HCVM$YUO?J>SG1?D_mP>AGdiqa2u#E%2!LOuL= z|8+^Mv&PEaF*#!;l3WDz~BF?H!{=(m!0=`Q*v>j94k`qp>b zyrf`yVh2%w0^cc7Txs+%x2*=p^M1#`ne0-ksm8HCSMc8>D=Ty50{UGe!0mAWD$sw! zVSi-_1OiIKz|5CDX3udmx@)oy4afkR6vRMt?(>Z(W$v8=ZG!BChlS>5L{+GmEzkLl zHK;EDlEu)2TEZD(yLROpBeF^Mwmv3fXQ6K)B5*zw?8?}UMEd4)e%#mF{^(j?!wFJ> zS-bn`jGPdWc|*p&Sh5Q(yAem+S$V>K@(8Wo`5@8`kYbW@2xe%QqNP!hkdi zRl`t%d-HlA|KjBrV#|7yGy|?w^cr})<==!LE{buxK)N48xzFmN=IwLmlTfF3Utyl` zkt9Kub39sSdkYS3Xt67(EnKUfpzmJLwewfY*2*C2DbTPnJ8y=q!Q6t zlGhlQmR&p`BG*UOzXB^KvIjF-j=oE5bmrapMwJM@cW~gGC>#uxnQAn|;n+znZbOS4 zlr~`I*(3EHtozTaTBv2+-$L_JL`^OD$P-A^EiIkoPwS{>77g1wodBi~C;3D3hs)1t zBgiicGxcAHq%ByZA$ppapY}#^muDeB=`$o_@b4^6q|%TCOFWNBAO?cf$XQ*W$9kZ<>dtTVqr{Y~1Nr_-|3WnAcGuA##-UySBWC^0>G~QzZYCt59HPi51ErIrAg3bvk z2bfma>6Hw7`UBU%wka)I{4O1Zx%J{bg#cTEj(k*&4f-mi!X1$1W1)g;VNJYS(|=z1 zxOL6f?dz0Rp6D#eW%6OeAwO0f#h(RH2r)JXlTs4+qopGEfrXU6m!NWDpuYyz*0Qeh zX@{)g-FrezzC3V>Fp(Nq2J`Zg4@(tDck>xolby-&j#97a&DEa9-&C2(&s8i%`|>-& zucr%wLr^tYc~P_t+Rlown#!(f3uTk3kWl>o7C9r|y#~^|AqAlyZLc}Q>LDBQ+SE@W zW3BQU=YGO^gw0WvK(Zn+OkGhQZHFJT{bOP>T_44j1vZC%PU(-U4V`)hDZ%=-JS0VA zPr(@Ua-c)MJNWLP-{G%fO{{5fMtUka3!ZTXtVPY=(+z8aH{4O8c=UC@8zJGd$BmUG zZ$Hk=US>pVK83olI~<g?qU( z85GO>Xi1=4@XPAbBsKMw^Aa<`3#Snd93}>U6I=k$%0(WH)z^!ZDX&i7 zC^JL?eXLw?)4R<}IXPDCEQC4VX`q^2_qew1rN}p*MwK>vN)gSeyo|i@wIi?H3z~5;^ zEmQ$|a01StwLtcaRpGh0O!d;O?Jc(zRmo_?u<+sidla~hT>BZ9YkKHyT5nY<;|4mb z)DNaHSgNSkYM8t)Q!NiuvXQ}(8*A;`$b^cJ{hd9Q!nOSx^mEg)l3t=6##T*W#%Q&qe(V(g`S$%z=KU*=hxWMNk0?sR1SyiwE;(X@@YkjGuoi0ZB@MK; zLaImeeq)ln$at$uvJyG!!Y$AAlaf}0Ukg9p z2l2fRre>s^x*{q*jooW8_0|p#Vt(|qCw0pgAMxYMD!9ga5%Ca!B#{R`Vtae_>))T- zw%+$%bj&|qmSZN>#A|qEqiev9admfnUUBoiwza*KcVsSJCxELDW~sm(3G*tal3~gL zSYgmw{x{|X(-fqdy*zg@_R*ToJcVmUfW=#t2xDVk**bT~e!R^Fx36VDFBd6M-6tQv zqdhC3&FfdGDBT=>3`%w;f^qP~F*vyh3yVX7W7lDv0H~IFYR18BK9h7(gjKI>nu&*r zJz@{~H$pp~@-8q?&o_@sVm3b|O1Awbra!+dWnId98ebR5za~=HDBVDK(68VGHA3*p zBSESdFtH;l?Mz#+myRh%FKx(Tqc%UFdq(ttxX--a_OY;5|Dq`lAZes>WM?B?*oV_&-i>FSz$(ejA*L!Q54^ew+&3pPB`A z9h4ceB@bh}5{emSqopB1Ft)aMa?EsszXgm_;bwgm3&BE3AsB*!1kZLsvWn+5AfbJf zX|+RFyd8TW$#O#0YX~i5dkE?j_|95dT2_y?%Hv;N>=br!b=7rX)#+c@Kr|6B_*JX* zZKwdD49?y;1Mei^hc7CLKG)g#u*ZZXrhvaUflcxaj5iUX7CSPWN)P;xJ;U6E(v}O< zQm@S5U=kyE=j#I(7}bClxhQMM z4~AbtzN#Jbhs?rHqmS65nXxvk-{t-m+vU{{VK*`yw8tFw7m1WhesDO z6a>Lb@yZVT0Ol0Z@jo@Eay83R(!mXjLi~Sr!0AuaxzWIB#y2{Tq@j0Qb*G<(awWQY zfmeitK_x=k<;@pJI^<@`E|$3Ljor3W1rEO;Ee+_6f}~kjiF0#?^9Tu093}#JPWWU| zM9Hi_Q}?kPpB6lC-{G6D!nWAcy0o<94+DzG)F`q_95oNQ%~*n1qh3o~RKr{hx1W9C zfSV{jYiWWi(o1;*>vf;SRD}tsOA>NgvrGLUUw$f@7<`qlzfZ8Od(bGC{F9>sG)4d0 zV0&=+yZmX<6)kXalh;CWxf-YH364q_Vrj;G5ke$F8fc1l@up#sU}LH%mEKhe=2}X* z<|*TBMk3#K@`gnJfkR5af_C^b>eTN*01|CJ&ZuboY@B!QKIu8;9&Eax(*hriYlnBHGh&x*JCy$ zg2}hmJ9R=ZkU!g- z>5-H(F$nK4z;@y9Qpvltg>ha=!bYi^E%Jq(jPL>w%{>TslGOnNzC}o4#FqGzCs+cf^QPsSKC(5Z8MNDS^OKUtwg&Xdp zH>mI7+*-Y!-9GuuGaed0XtZKt$lpGaCZS=I_zw~ys-VPE%ZtkP=tFQFwI#gAU@^h< z>TLKi7BP423_2baUN{YmXC$vvOsalQK@Ek0uEs5K7*53^J`ZOoy%lh!b3`bsCG>t%IRD4$tmpky^g~ikWUTnl;aWJ5 zM_4q6I5s~JTYc3GwQBN7=Ad&N_m77B!dT!@tp;zHvvvpBTc5pV zi<@+5JayBxIPUGhb8w+ClHNhGy7OyN$ZRL17N;%|57DWu_&V#YWSr7ey112{v(6O) z(KYuy!}Ovh*CQdpGrc*lB?L|XTBML=-Xe#U&B-~1B;e++F`O`dGjQgosmFTI$cblc zMJ(8hD`TwS@|L0n8siUOe16H|nR-;sxWirNt_Y$#-_!Ii#8*Zh^-?&b%bGwhje!hQZ1a0$Ls5}#T#OpOS(D`e4-S0J$}E|wMj z{*mifq00=j+&PA>nGfDK`961D8EVN$Q$Z>!^up3GYcTf2o_iA?7~PhivQot-YbQQ< zrW|XWj(MoT74q}x9oQ>GeJOLv)>JZ=y?8GC!rpr;?C~Wfv3{0BXpHWIuJHGBpsCM9 z4Uz1%cbI?HBP&C;`6$o%iiAJZtRvZW!m;}hr`Hdhs`5RWTOwBk!Zm9@T6p`4f-4MS`nm5h+kZ6pg+;FrH;wM+#0JO;CPvr_3$Z;tKUEKf^#nD2P&upk)BkEw~8j zeyo3i*_uYp5=$OppV~%;0y{KW5`c>Q;W>z*z6?{fIN-ax5ylEkBZjv`YJGFTD=v1p zGh)agX!GLvb9lc7_>UNFz=X{g@N4w|!XupY(J-v!B*}5FTB?{g2%eUrMeO;=v>~#z zpt@8nKmA-R{U9W3Ip*8G8srxQ1)Vs1G4pN8mP&Q;FVmK#S3|sTV^iVOb+jU;zk&N+ z%TkS>H*m$L3n3$c)-gm8180zTy(AX#frl%W#g-md%L(IJL+57y`!9~=55!G{$!$FI zz`{==lZK4GvYX5#U2Gk{$q>sRGBz+~!~IG$bKht-Jv}5Vahtk_5r)&@wH&>`xs@$8 zZ@nnUB$F~U8G~WY{7Bl>hsrGN8n(DhloK*I415?E)#*p&tZVDlJRN1?N1O_8zhlCl z3_vgTmCXD))A!hS3h_IZ8t2Ar&xSj?nE|N8^0?romGm!(kxx5o=p{HL@F!;Ns zv(~>$W=A27uc`W>9QZ3J3xXdo{}aGk7Bj0K3b|0p#3^g%CzPjXrq3Du{n`$c2cjba z11S?F^WPQoedhCvIr4{3_$!39-#_c@ixyo z9XlVDTb<@GXpMPNU|Q`pqqekGTok26qiax>xfj@c*2E}h4h4U+A)u5~1UC6Fr| zfG4;&@PCthRdwMtB~e<&cL$r+N%`uhFwiL@vTNwlm01g(4BCVaPRwrV<@Sv1)A588 z#c>AIi*9Wn5V;D5s=mT;LQ|~@o-LI0zhLjv@Pu({@FViZg|io5bV1pF+LAIKHlmmP zv&b8jsq&+IyI&_{_gFjp7@*EQ2pQt&I7ZF&1xi3KimCgQ{Pw0O8@DNidmS@-lTCJZ zDP$b7lD$Iq-pAf6At|JcBt&LriINf`N~A>5`@Ns{eXr}ep6j{(|Hg5j-~An*^}Dej zYWvWg76A-T?}Ybu%F=$hu`MH-a;zd>;?D0PxIAz?eRWF%SMvwbElz&DJp)lR%)=RM z3)RjM6^ifyhy|41ujybGd$~ys9~~c0{rh_X0jj}#&T%XJk&eUtc(>fI=bqLz8nJpr zUoU(MY=8F|JVa*%^W8;1;RcqQm{#RM7A`|k6zd5!hwoa%m zxjB}WCcY{@l3_-RR`=e$lh^kit=xb0=uvR1kCkp(TbeGn29*#u`7E!nk!?+^uKc1f zPw~%}zy9#`{g?>2fhp!Ta#Pw}5WUZjgVXru-DlO$5idWB#g4DXj$&h{J{u{|SI8tx z9C1&1=BuQ}d|7x6B7^V10Hp2|v|e%WF_cm(XyG46&n~Q`->RV$HqhAL-#4?fOLyoE zPpLvRsEKE{CltqcD{_)2m|0oHgFKWVdFp&UWp%{4QLi+oS&mE#e%>5XcIqTW+T2J@ zMRvwTX4LPMeK3#=}D5D1kw9G9~(Q|h$)WXG6;HCIWCvl++_g83JJ1Dzk6qwb1qjU zdAsHcc9uTuT!I!4>>^A;LPF6mKG7x-FEKSJr8!BbG>@S%KX~d&yLV*tVpb5&cXrm| zV0VSft=%@gi<}VO{at74G>_CoNJ0r`ZZFo=TB=d9R#pKaWbo_UE;%vte z=GD|Bg0B-UajT4Mmbfd1Kj(!I%~bQ)`*bVTM~mLS7pI~US67|aHZ}%!57Yc>yoCq* zuWr-`rHcRVgSko4>vglIWg{w?IxzaSrtwC$bp|GAkB6X2v;#gn5@jPGAmIM~N!Od` ztznH);viu`3PqNU9xVaUw#;e|7QqV@&!N6$00du%T77NcpOP2Ljp{y>+gh8=Un)`G z{8`JLBYiGMpD=?}ZeSpRLMm(@TTXx{$1rmFlg>ZP{oRA}j~MsEdj~?Fn3T$s65+RVp|A9ib+aieEZO+_!SZ+8P#0O5+k)GE}GtUVs7j509hEX*#?E$tBX4P{>k% z_zD3KJiA>=Q#oV2?nHh(;+5^jI8MBR938ipWho$hGR6{YouZy|kiLml{v+u&dyeyr za_~+vy`X8b(nlpFrSIXrwzY3vr7N51w9EvhT=is4;%bC=x$7n$17rjeXiMDW=hoJEU-I`MvQ*u6~aw4u^}&&81sgUr!0z`WYUy@tP9%4U4UxW`4$^ zLTMno6R4b!PvXi#PEHQhfU;+qI%$x{+g;IUq)cMvwo197wZ`A>-hZcmqyQ4Mmrrku zbvW~B2~(U@Bjnq&{2TU>ENC_<-<-Rih+iYxa^ci2KAM&@dwc36U5tp*;Q8qZYK|0` zXB%(ef-*3{JskaxW_c+dp^>tzL-UQ62nI?8Kc_c3&*OthjMCLFvqw;asys?aZG-~c zxfM3hsv&XFWe+>Y)}8zmNCOsnqfWQMg)+tu|p zDe>0je3^{mihKG6_i!EdaJU+po6$?pge~il{EBWljgr?)`-6?sWtWO1F>)Wpl=JXaEU;wN8e zKq4(J#7~vOj?TqoKc)UJT@NMzLC(U@6}eTF@+8qzp2a@|7wa9Ri$5Ix`536|w=u!; z_XCOgv;FmPr}jnUC!R8r&6JumD7Y8UWrsWyh8W0Dql0BuXR6{{0udEY)`Pt9gA{y? zwx*9iD;MVlek*)C6QhBuj5-#E$S;b@sf*i(ZsW?Xv!xF`WgCShYhMU=Tv)xy8X<9` zs;OxeGLc?Xo*oF6`(*q49d6ZC&57?a8`=1rS6$=C$4oik*#ZBo0`RJ{@-(s)O2^*2 zk~W&+JPrsv8hu!buX?#1?LGd8+rB4Q*T8^m>G2JTzxu96Nl#wmG9G-2mQ;~sy~w6A zy^co=y~HXH$#_vLHw8U};lyYG8`@O~f3Qy3g9-imm!ii5p^;iD83~Cq@Gz134D%C$ zvsR1NywrJ3A!#kQzOHx?Kb zLa9ceWzJiF{i@mO%+qupU1QTH5HiwS>v|5)7=V(Y3sL*@PIA_^wkgn*eb6pnC|wD+ zXf(a1-NkRrm)TfZIqtk^g}f-Lni(N>Y7$wKPmb9JqS7KlgiZ3~P`@gt1Px6g2x1Tn z%n79B2jJd4gVYx>c4lcN`XyCW&NJ_P9{>Kkv)GP|?5&`l^2<OQ-^b-mdMvbXAes;u7@m2{Q&3emH92Q?)qDIr*_QFp zGFh@2JH@kJ;3bOZw@n>#?gW5ccR__Hvn?+LKE%W3JM*%MLU1U*1`mGPd*gdbxisFV3{iy57veE=A1-O~jc+f+Vjzi2M^wOJh+G%_vEh;JO#~$h*_{gvTp; zliPogO2Kh&b*OuTZ0zzCv4UoDQrx}WOJW$=XC?I?k;4IFVG!}$!wQe`x8Cx8C~VP^ zWbo1WvGsA8p5SaY0TkJxG^W}cD^C?8em_u*Hq&O=ifwl~kuKJ}9LyzHVnk+DzLQAF z5(_QSF6fmtj4fnC>+_m=rDP!(=|X(lXPhZI&ndq_Pp70}lYb84&_H>{13Q-uUQ2^V zPlczUwiL;EF(QuWfzLnszXo4yr_5nPxm&uWkXo{#(X$R3u2+>T-JwqX4A))XLOwGh zw{N?TksFfEQT#@=zH&ObO-jElIHsXa6;9rY_^j3aq23`-IR%($c1*Vi`}+Xa7peM?e0nFw7ErKMiD zrRgNb7&rJadS!PZva*@040QRda-M`Jt#=oWjT4o)8rqqY-=6mr^}-TX*{C37+-vJj}G33oZ6RG2D|1m^epq4ym71`3bJ+mAmh{ z=uiF}zJQ(Pc}Imw0b^5BY8%JBtL=;Fh}98E0EBf0RkdX2uXZ3csxLeI^7kbnG+nr6 ziL8z$=!O4F#f5XM!%J&_zQ>PIX|PVzL$I!vO}*>+MH?Gus22lgQUiI`xbHW4%F#O} zHlCr&Fv5+Gz(l~I68s$EYw@0c_B+p;(=7Q+uGkn7h}pm3$DsTJ0+7K7A#|OkX=!d< z7ZDX53M7Lc-JN6UYp_&FX7eP}DI^*I;Ha&?+M^Ds1X>tN{kwYf-0ua9`JTZ}rFL>) z3C*A2=;+9~otw6=%zBMl2V*VCX<>}PjYM>K%t1?F@mgqMV6&1ZOpq@%!ID{9>jHFb z4htMCK$q^>NDYhAkdprJyffE0zA0&ZeO~rTY1NMES1#xfFvKI#7^lDmrbZ_1Om2pi z31g)b9*S~}Ywrk0-e|ov-C)l57vc?d0N9~{0Rnjm4DT;cJbr6m*(X1UYEY`xSm>zt z;MHIUmJac##B1@iZq2V%7l;>s$M&gvkW-M8BiXWW`ZSCav&V<_8&qs5h9%{k#25ri z?0Bbmvh->dL%(ywuAT%tQ>)(Smt<9nQNz?YEeySW&%V^{{q<-`cIr<6fgw~NJjjG= zJZ1u%!9B}b52qv>QDI+3qax;mjLWlzhKAgsKBAJvie^X^%G3R}Lhfx*H7wq^7D_Qz&IqvG3jaKpPGjctrS8^ z@wG&x=BC_=jJfZ{YT4p+F@5s_ZqmhOHa3YM>bMZdKBq5Lae1JQysf{Dw(WAR;BH=C z9-{eUDy7L0QZy#}zFW71 zQhD7fF-!VuyVrhr)~jX5@hZL5=&ybIRqwISGNNFck<^{yE9WS_e@Ba~I7(3ChMvi- z3;c93AnR4gYm)$5`!GbJ`EK8PJmB+q`w44fgg6)JR%AEfpz(=zR9qkxzV*$-CtUEo z{wFJkn2@uW+>veR8P-o?JUD|oYqKKCFUqF?PWnQX{s3}YCXTT>rQ9H%hmG0El?2-L zYB>aIk_J2ByWImg9Dxpa=?#xH7O&(j2nJMFt>Jl)GU)cV*!Lh|)ZT9& zndqb8Cxc~6t2*0MeMGX@cqxc$fjH=T$)p=0KHl2r&o1yjj!aIj(@EhNna|5wG>W3h zdC=qt@qqB|q@lB$Hy~MTsz;PSc-9Syuv+9XVj@P%;MU1#HU8@HY#TGz zt8t48xKy3*&tLQ&hD?(<038@0Zca~@HR0{o#223oQuY~5=xdC*x1wB&69Jo8_IUv= zKP1?}Y3VC0gXekfT@b1K(Tx=+Tcn;Rvo^B zbh{>#${ zp;}p;C#J$loS5adaJpWtZ~4F(=L$8v#U0Zt2nWG~{Rc8ccCw8F_W^VM?w~wzM>~Iap5<5)yuYA3QTt zqf61MCw%ip$K{}k1gw%kAFLlr@tC2q$kd4{Kra)3g;#!yMHHw#L=|v6r zZcxvO4LDrZgsm+sVqthg)}{f{Liuu?@-oVRMk$9Re@v+~isTk~5P6SPYBaisq-% z8X-jHiNv@7)BsZ+H<~=9gW$PJ-g=P!g47_EfW=y^z(96aQC*!2QG#USa0Jje&Px-e z4DSliWoSDW8Bw{O<;_WU@7{d-SONx4Ax@csN>|O3Bl7ii3(06t0fjF`1#FHb3O6#< z{~b*sxyGBPcP0d@9=Ck_ImEC1)Kmy}o`m?gWMP|ufp78pszTop72B3O%z#6TyI@dH8L)_13>A z)Sfg`ehGta3>?vwUG?2tS6`AYdCS9)j@oN+wzf|AcLjD?c(J9R_5p~f`}eeCQf1O787KW)hQO@|i~@;%$yHon~~GZlY~HSp__HDP>IUuEQUQJ&t@3gHo%(n%P(2RjxV(%{3R zKKfoFyoL6*7y4x1MK-MHpL?32&mRlAM|=pw&vgI7`)?X+Vh^3&bMTTfW^FHNKfVe1 zeV|Gn0HbT*<%^32iEf-Y&K#$<@^or)`g3}=1wg)_LDcZf`l5Zp*q9+KoZ@F>gfm95 zTDrOkKG;w227V0gnQ5_C$tx^`Ld?NSp%wEfp}R%6^n7W{E6-Q1Tx@cl@kq-H1%@uD zfD;w(A$P)|7n&)BqdfYcxj=DF&(*cWVjo)NComx}w2^s=@SM@|j8E08btY#Vlw@&G zGbPM;0#6&@N+q>FJ2FsAw<}zpXnuDas>6r*rKyRRE`^jM&wI?L|0Jj4_R8z{xWxs>{ z6p5R)vS!E{9V8PH6}raQUOo6bVu6|D<)S9M!J}0;fpS*7Drim!rYWe;YaT#$!_GKT zi_N*l)c(u!gFh#m?|W6A9)A&?2@~_8@TzUuSZJ%4q)Vx)szMnRh-m^9tivtDHaf0Z z^EZT9-K}UTTje!ga!YuX;VD4jEJ)To1Sq~fnzz816K&Y+8oa~#gzm$pM4+6Yy z60EWb1%<023DOUo;z&tHiIa?DgiD3DiH@pl2LE7M(8 z#a&rv4<0 z!HI3#TbJcc(GN%01G%441F{caAB&Z>_0S0~z{`njKGPW!ga?coPx@TXu)K25rCjVA zb8Cn8OBQM6;USC^d{|x5CUeFd(W(~mt@XwvH=^JhEv>5&>f9*ts%jEJrtsG4+=L7} zEt!H>n|@Gl-;BVb!~K3Q0x~^ZfWHOOkRR2XT

wbS=oBST(FWnZ4PpK$(UV$=e2%?V6ApvY`ZNq*V?5LP@T=D z6&)Io*KAFdpd}GL}h%t3V2Nd&NP!YcEo_vYixec9j!DdQ&I zKg1Sp-vR9f=a{6Sk&*6T$`ga>4IVEd?dt4Mao2g6wcsN>aO}rlRwhl*>dr+EmPx?w z72IQgO`l5R(%I`PPdRjTbrr+*`FDa<{zmnQ8x@QUe%A^Ms+Z=cuclMF7qHYPR7D(( zBwYfK1(g$M7uuJ^Ko)2L)(E!U(CcAKM!`8jB?FF<4Ms_pzMDV80Xsx~rig!kR!C3) zz!w=~{N|jBtX{l{&xM~IPBRbmPPNJl*On-%N-~q%KCkWSnb_Wf{r@{uvlI(y6JfZ- zz|1;7<4*C9#1;cU&)XKzG#1ElF`DiPxlyJXMrU)^TH)!;CGmowkHy(?#;?EgOvTo7 z7r$Ra{W-6GOj{svHB&ExHKpSME;;7Q- zM5hTGrex`)^F}N1g_CY`lP+#WKl+P40^7k`1wX=XM3e^Iq^9I=lC&+FaID`E<4UqV zan*$Q`$723ib-3RyKHJ%8T%7RiVFSGc;~qn|5Q4rOCKICHK%Ga*!OtZskh$|bgC%* z811ggW~AKW-{co4e2;Pj>=7xLEl>6h-+T>rFR}UhXPxbg1ZDkGo_5b}y-`u5bY6e^ z2*&53v3yb5w4o2aiZs+o0D37>j@ASMtn>G8MDvx$)Ce8#Zc%M=(HxxVVk%a*mE8P^U*HpQQ1*}c!Ns-5j%LelvV*l*-SFW>f|EJUg zq3GsHU*DykJadTqy;uVaDq@I2>c9^C@0YFb)ju{K8J4zf5H_CI)n()L5ZAXVEiFxh z_4T3WD?{(pwi)3_W8s^o?J|w1;$znPb1(uRKs-ngoj(z+E}{X3rM#a6`Ktm(1<5Zj zkfU5rc&_WWeM@ul>w4dQ ztV|Em#u*u%L8OPUp70ysG*8ovF-R*to0p~f2PDkQoeNw$%RLu&Zgu1s*e-fwwqpmd z4Na-QBH<6gcI6DMdt`K!_Rb~lZ+`j|X-*a#t%lsANT;F^1mCTG!T=MMOlz7u<-+bK+-- zyHorgWg7%x?++m1RX2Kn7CfkcH;xKv#8D^5DZ@?hen7t{og7|J_)(r6N1eNYqB%4n z$6_3oxH`TBYM*y*_Fv@(-RJrfCe z0Z-8>)F;?$6pJTFQqFeq)xBNr@g1+Voq)nzE8`Y}cm-yR`@%SaiXDD%B&+K<9 zEns%zaWlEN!G1HdzVn z!$lH->L~I5%;&G37Pe@Q*<2fs#J#p_Yvhr2Zj1c$3-Ra7+`TA4n_XnyGojWVGwrBM>VSZ*0$P!n#Z#{1$M!?n0lXhh#|JYbLuAud>G$ zC)ws_|7-OA0ku-u(%*DHy~uh2-2+jdAP_%RdP_FmzbZt0`(l(&%}j8e^^^62D-Qxb zo9?{ty)t5!o+0S}Q|#@j#2kw_S0zE9e+EDkitNccynx;JL*N&tO*ltiM~q$iH}=QX zL?>wuAgGj6VnSc17-j?F0OPPSLWfCN$HgXli!v<4!`Nby-g?P6l#&jnI-q0~5%)h} zyk%;UtbpS^&C+bMV)) zSEaEhhhR!4-2ykK@eoKK+JC}wtk?%ZK1cCEDN5Yvh&%?3Dpv|1rpFjRgP0b|J98;e zIq%GoWxc2^9WSSvXGD+HV%A?e0k@8@xUf_8!yQqAbd+^xA6^nG#QswJ>grGPWiRx> z^|$iKO&`3X#+bD=oKV)9iv`zfbOnLzahj>Sm55r<7xb^)l`J^%oA{cxZ>I2g1_8VS zV06-r-j-L_{DI30>VxftGf-jTUwJYtYM9JTKXB4^a-hPV4{3TQ<{VE)r4ezQ-PDA% zF+^H|fR(m%RK`^c#h5}n<27NyynHJ(5dtig$a)~uQjSpdhip44A*q2nHFo$c{HgWy z?33w4(ZG)meDtllP^sqJ&rH$>(S~)Rqp|WXxB(y-q#zNZ?A%S8cQRYo6xpc(iFLY_ zONFOx-`(J`3*R*|9f3zJ4R&%q35g5WI0OoPg-b(Qs|iU?sQ07Zv|qcxt{O@MuLSMC z!n*#?Vou@x>z@LmBT7-5m-k|tX4?SkdwZiV11vw}1ZH$s#w*Mz2PL=k>?g7s(~s2u z{ZK~&dUOT}t!+_}f=v-4F9Y4Vvg2GnRX9F=`|X~7hYhY7L@3!C@7V#V(}tk{ zPeZo&!2%on{l;z2CcWi9L5oC23&^!SOa0P6q*awWIyS+3hQWd)*hG`3SNy-YK7iMu z_S#+y%Kgkfz9Psrd6hiyBF{4K@A+@q)pM$xhQcGQEbFQgE>B2YZQh~GVmnq z2O>`z&DA-*bD*RWP{-IT{3LqXSsgyZuW!p~fC#3169pV@tlZS5&F?`3&XqZ_Yo1xg z%>M)LO6_KpUgBTIG$JT#0}yoO`{5a${YDn`3`jk+!g2S^1`~GlG!Os6umRJ@M&jk-2pN#-Y;>mPq4v{b?|t%iyV6$sDFS{_mYySfM^8hyKV9JaP;Lh>#fdkDhf+<1Wb-lM)tRxn!f1WItA_Oe|Jd$NF*hY zwBc^4AmTOFbP&6C)rf(=O2j=JB0#>w@Cf9BwmF(M!cyUJnXs|7du$SN%9))z`u!6zVTiaaoCatakV)X5@;nleW9;4(lXJknfe?z zzCHjaeh(N@=77&H88})~+G5sBMx={H?8KR#I7pzXp!Hws^yLXZ*u=XtCtRU@b6{)k z&ddX+!G{gJqcR0spMW9*kB8nJ(c3N9F3({+r!UVmKN3CP3WNfmG9^q3@ksRD0B_t| z)CrEfgOh^-DZ;zb2Zt#herp8Sdb$(wyFZ&_%zrhG7f4s{MN5#V!fHB^^lkPw1)@TP znvlAXKhZaulpWg-&uDFJ&8}O0RfYS)9xwej5GQ^@rmOe8H-k*{G@fjYym>M@FVT4G zuRdRd+SQIZ$Qc;`-pg-ogx1f6k`N!o9jh_BE(zs<7yZVgB(cM5p~mu#>2DYnb?3-D zJ^*P5JOn8@w)Pe=FE=~nx+5_Th!Pvxn~C(z<{p#F6f0lN}>(iu|`AcA9!dcC>>A$TUXKx%$i~2$G66z+w8gf7;9?OmC&$D1py_Z}qxX z!T$oZv{3k#!3K$fSpw9d67^w^^LXkk+U%q!0Wj{p zuv{8z%YAXSw_uC+Y|Tnx)U7Y7eGrN99k?y1h1{$esnJZ)BZ>9nD@9bkwWCDwjE`%C zlx1e%d_wRqBZvl&P$2h(!tvp)hrd}8M~$%ou1e2Rba?*@Q~)QNxvar-WTqPj0Zm2o zyfL1}f>q8azb9c%X&{VE|J|GjdC|_iDSz3yTgGulKU1ENS<)WesOn28!NS)b3+g6E z0G?Ja=e$_)tCtLNG=ak*R3w-B$fcDTlE>2!;g-f}Jeyj)8Du>uR>Cl4Obz)=4xjBWOC-skhI3 zA3s)o$LIYMz{1q^uKY0sbfD2_RI)03QI_{V$U zum0hmo+pO_$8aa6l@)?(y1Ke{o31ZGho2oN8K5w(q35!n0CX1gEV30SKZQTuoz>Em zmbaJ*G{e*MY6mW4s}V=TRdiJ$yYB%kns*JRyZ&;~_a864TUeq@uX(iot{ftR{0&rN z2IL#3L_esh(IQMch;00(9@;XpPf$yU6fp!|2Xa3U&q?0hbdHFcTmpZ1NCU(s!YB<+XenLdvnKDAS zs&>Je`T+u6!_`zD;8IN`02J@%FZ(csYbiBM@CV%Z^#1&=@b9nienZdWmQ6rqg_{U; z&aB~El&UjCnr3mRMKBARmOX=zw0K38;uikpaSF;oyUS}gQ|*-N{qLy0fFAw{b`~Ts z8#F#*Pm^Lf$vNo_?AJU@iYYmEVt${zyQJ2)nks4rR1xGdCu8VEy2jNqqtzZ5!^2nY zo?>QZqYmV?hW{2DGqnmY+HP*4Vy>>X3F?w$6IoLF&A>Hj=;GBu`%zU8B zdO|MO1p68=|F9Nvb26K6`n?-0U%|O8>Nv z*6hxp;5{4aJN+qP7y0wms{`fzloTiCdW?``&aQf1OwO(#oMdRasWs2GfBv_=Rowih zC?HzkG}8=K|vKJTShj4PO727e z4Qy|jKXlrO?-ea3=&`usYbI@64?Z-M;*E2b$XQp#ym4C&hn)Ick1L%cy^@L z>BD&OAA;$xY##!!@v8F6x&^^kP9`4(o_ltXUng>=_)_j)lA`*!4Z2SBvcj@Ib+!vCB^l{+a0ctDY%bKbd(8<&9x z*%Pvx2-Ek%{YoEC)bg7QWpttwGmEl@MPsJ&6@wMV3_XOxcL5j)new1DvQwWQzQET= zF)tu?lLSaZsd62)=jvd(=j{==Alum31v6==Z85%&NUJ4X9h4_TXYm~Zg3jAz`M*}7 zZ=Y)<^&*#$3T&5pNu19mAug^5nl=~@|5rL_8rLsL=@S2qw$0}(Go{v?ENJwBp!$I1 zs3SR9JVrTZwcF0|xv)RRtl-FisfT5g9(qC?z^)nOvP-Efa>>RK;?f)z4Q$2UIq(M~ z!E=bnKz47$HF9HhP!qm|&28QCk^LerUGr=}qJbu^g-sd7-Mz@12%NL`eHsB0P6B=gn)?z#OqS7n zE1#5v=Cv0DgWQsU_YB2G0+ zh`g?L5X*vU(pwXtpgbCeZOW+4IK^%?@7fnmMc@DEM&KKQ=uO<|3s$a42W^ShJF+DA z=>ai-L;)EZ8cw&l8ripJ&iLq^hNKr@7c>8M&wK`%dA>@X0!SV~lmm}5`MyApni4M5 z$uVb1;JM-rSv0wI`z`M30Kl3MYuY6-5b#Ur3B39>ED2I~?~^$%l2Bt7u=Y2w*OSMg;;XPP!5!)@)r8r`$Cd=s;3S5b! zcDc(0`U`p)XOKV_ygtyoAJfz{ptf_d@0bAz$ruDl zjqS zmt`LXM59_$!|V5{x-_>eNcK1vFD1?_(PyFUao-HB(XpDFMMaN65Pva42Y+qoLmdJ%ZXh znS!bL(;l|D@%VOP+yFX3Ix*1y=noPvc3dZ*IO;J&kTav=UF{TZr z9>fM9W1Up0~k z&-LTaf-Qcp=iq5t%{Ix9w{lPbEFQ@8R?k-=&z+@UmC!Ip!w^RLl{>%>eig{tl82lD z4PdZQ2Z~H#IglO^T!DRkNg@g*vJA+7xG~ zDkqwOfow`}NvJb@Lb5nZwNQaB1z2R&z3245!B4Lap9oYI@Nd6o{1i0rnR%p*(fZjw zlK}jIQws4=>+l)Kvg&fvM1!KJMrc|#WA{E2%l>5D4wwC7`BEy-t^m%V2VD)2>jb82 zlFacIdB)Xn!tN)UuH$OoJc~iMa-p z-AGw3wbVqMqb;mcl`iY)#^E0X$jr;nPw^`GrBT(L_@KT`8do&IR8mK~_DN?%K~niE zPB9$#c}4%VK*Tm;;ge`=LLsyVYCcl^!c8p-!S?sr++n1}4t{ zZUI1Ij^ZJcjTHW7dgAv7tU0DvURwESgnDqF?HzKSA&SR_nyjBfwnPm?%c82<8FT;j z;pD0$Q$AKn4po)X^cGsKTAA8dGr$kl50_@(X}p_`?RS*BPIScSUy$u&!3A>ebewCg zhcX&uo`cARqWCe<)AVy2B+qx5tbR>kgnVnG-!Q)`HpT$I1egm26uQ*!k$D898aMpQ zsi>42zKytFahYNUQs^SU_Ox9AQfqw^L`oW_2 zn@+!4Qs%}by={_Yg=>4Pjcn}bdfK(yx`K`-a|j~%(0vB~T=+pWEFH^#z^2+nx z?p3(AZN51Y;CX-$wdk~lf8M@*n_0@45Gd`0BWWU)`IIuMl78$PZ}}M|c1mJhq9Lqw z_07X?NEG9Aqop9vAn@$OiyMekNV`8tJ+oUwyEmkTKs{|F8LS2J!?Du*Cto0*fzVXaF)a zrc*O_1w=%RR744grBNF~l@YF$J3GQ?ov!sAvnI|+x)^I;=O5MzZvkW4(&sh6JSt>1q1lQy?^6)PRmXvPOO@Od|joczj#!9 zKcJo-ZLlE`e$i^&vP%#V5a5v`6Yg)5_FgnKw)f_t^L&Fj*i$k_Ld}P-Qj<5vq~n)* zOWrYMM9+G?d361X_q$G7p!zsK0tzy?mre0w*=?EGYdlq8#xW6FZ2#NVURRog4b&+qr_sDb%anTq&2FXiC;8FVL0?_2}= z7g$d|z|EvD;QDPYzu&b4m44xIcGTkqL1(WF@rSoBdLv2}B#Z%ER%svl-A#B4xz_*i z;W*8{o3d!+8sTXm9wv|)Aj+5SM#p#V79pJxWtwIfXmpMr2NDqbd?&4i(bIS3#>(2t z20sjnot~-;f4eH;H#*(W0v{auDwuKlRa`L5(;VXZKas_Wjv2@uf+-kIW51;md)--1hQ-HTvbU{WnJ}^G1($R1B6t z!0R+UAQ>5=P0N*?U?&3^NsXmNf#1jqj>=a2)4LECHyEVJv{wOL14_hkvCWm2o8QtC zgbHKhP#&ZD)bHYd@z#6)37Tsrz^HsMiR+DD60^CUO+XSK-Z{e`S0%=SlG^uyu@pqy zonIfl4P>KS*4eU}Tppzih#H9vQBn~oY=+?sGW5^Fi(-?PZ*XXH<&i_hUda~onS1kM zU@`a*+yjil9^5Jjl*4-sSQ0_gQ-o)Nhw6PjADv638T3f|6KIP?!3R*?BmzorVqipJ zxBb!wn9Rj9 zpfj%UQDngNS7?Y-z6#nBFcIQCKRLht;f1?WeujfrYE80K`Tl3Xmxlmy4_q?p3N&8V zYWpJP*;G7{*!%|DQ;#^eq3deRYSJ}%Kk!$+s3D{Sr%5&BwrY>GOhxk3*JaX1_}Xapw;SJ8c|+!(0){t)BdNpLRgmg zX=BGuUidf(bgAR7tn)MEHrfW(3p0r;+Yyv6O7dWBCv(7Ku+hJ&dr3B)!j z8lXi0k)1R{+t>8J0c*EZ=5p?ZqLP~u=eBh2n3g|)jI)O~Rvadh3QBJX%^Akvc__Qy z(8sh_i>a6eQH^FefXf2G-9h^*_FCYa0yCw*hERWw264{F)7ZqM^R?#4cGL;#4Ks7| zYMJ&8wDRCQ)uO_MUp#eH&w*NnO9sP+;HJ3qNU;gobubsNr)w`;Hk`j+nlinuyGt+E zJ!*?|d_+?N-!6FTrDRR$Co%g!8to(slZ6$rZ3IV>u6YhHCI^0d$AVmS=hS+s%7aym zkpfiqT+EcdwFU)9Kd}On4;YWY@U=Q@uq2r1M5mo39yZTzG7RGYxOv{FL4W~r!6%YS zg*h-P=KL-&2p^D=%k{_|&C)C17FjU(@fp|YcM{c;gE@eAAyx6)bo~OCBMDJ`@W4k& z&=^ucxq~B$KYi^^*GwHbHF-O!^118F-2lt@5p@TDCD%2FlQ-lB8>_PrJs9r*j)e~k zYMuu-YfMi8<@o@}%>Zz8XWJ4F)Jl0K>)0=i!y>QQ@%Af1|G~&32_c|+k=P^cYzLg) zqLi^T*b>MoD3BZ00jxQ1+*eINF_?j|1vyUaE6;>_515XTIG6ClZ&av`B_~Nr`tud~ z_?zb!K>tJ$vJ$C)C^t&<8+sHMkH-#-iJ;KwjCX4+zWfY2S{*+>sml-Dc@Ab|iwUQ< z#p!KPQi}%mGJ7!O!|nmdJO#p;u`3seD3XuxVkfIXumW+283=Tb+9qeguz;!)_+s%w zBkYL~H)Vnrl0X9%JYIaQrMhODyT4U!afTS}Lr%b#P7(w24?23jbyFa+2Uts~QM?rT z{ncgGd3Ro5Z!#7bPuPQQ2IQkIH~gXHc{4oKVOVB`>bvjWFNHih88$!+BU+jEzy}PkDiqbh-s-g z;m%HiN!IW8cPF?Vk>4Xsw3Lx}kXfqOxaLPbB~DaS^uG{NK$}*T_RC!Mjc|7au_B}L z)?>eO8Y=5^kf9S??n4lu17^|qa>^~Dt()tUwo#OhTv`|D}gYkFV<@;w>@lyXjp|#uyMAA z|MaBgAF~x36L&KTlUY2`{3ieC?N=DV5rf|;V49y0J#8_SrK1VgxQima>y6*DWt2-ES=8)O`2~TRD&Kl`)gWUQJcm@kQF+Nz@%bwi#a{K~uFjRpkMxKI$IE|7 zNI-WXl9iuF+cLIi`?(I>aSd6*bl!_#&A$L9@oFt6uFzCIs!W#%DnI=h< zE#_xT>)#6GWv2Ykj9jJ^fS_*xHf6oAtn5?95lwgMT)zMCwJ_TW+!s&{)T8s)KM`tR z^+GMK#}uEtC2T1QYY#Zw5lyX`gM+xd7K(TMrDSos>M3pV_G_V@+d|}sY8Q!g1?epl z9#8fWPX9|R3>+1j3}j<%V;pb5;D)I2hO9sq!7YC^v8%|e4bfEpG_sBXy%(~GJ5OE_ zV}z=f$}M%}LzSeFft4XxY>`Xb0D*wD?!)3gNDpX`S=s(c{`D+T3gR}t@6e0Hm!($a z7>@cYiIi50JB6Xa+YZeO765l(8j!nKKMp4Gp~(TfPFpB*5|@|#CJwLGf8z^)0z5Zh z4N;8xs~FqQ4azGpC{BO8)}7QqsZfGt>F9Vnm+===jESHHd=^81@Z&GI3rC8KhZO?~ z<>NqX=mMuSrpWTEqC`UV_2!VfpqWV?@a72Fa5K-Dm5_kQ+wPz-GZ&$D=tE z%u~RsMSy$Tg+@OZZaD@~4suKC?^pMuh)~_zx%~W#bjCH<&)7k&6BLGZ^0gB?b z*u(Xz90S)c{wIlAJk@6(UcQH19rd64Jghd;c}WbgSKyXlS1LBz6Py>7yLTh3o0RA# z|7?gNJF@#a-25uH7qL{fUim_k2goJYNSxYxXbW;tphTnOP)5T72n80zIIzVc{}JA+ ze9uq*nWj@g?k4dCrd!~T1@*Tg`!Hale#=1%TMupIa!j1_90 z2LI!u@un4O=MVhqdGFr{BPdX>U!ZAddr|^7zQQ00ShCI6Q|>ACjhiF^Px&dx4OE0w zJesfeaU$m)VV6_RT%I~$w&iyvU`Il5f`Z$hx_Wh6)r8k1@F=I`N`b<#m(_O~U^^+%dvX#?6ModQ)6Q$Tb*q715Sh2(BPJrz65bY(zm>4mZD|cz} zp?lGRPDK-sLeB4#D8I2C&4{pn9su_0O^hv^KOrBs|2ayTEBb#hG=U-K-*F-vG&`24zzX zK}FSe>A~RQqsM+`Bb!yYr_IGn51a@PSzhX%1`w`q41hrdJgnl}Se7G7x3< zKve-o12r<1fisX;qYMS{6&JkTNSNjjtkq&|>wy_wCB&tZnAi$$P4OJI(g|1}VPbC4 z0>oesxmGcTgp+0pF71Me-K3FGhMV@QY*y|uce1Gu)H80dc4uw5>48N!x83)T7Q?}>7E0X|gKwxij@y!GWs6x;mX$#e7Gp}FLuIOoqT@);I0IA*e-5fj z^xkNDK7Fqg*hMfMsWqFdI>;rbUM*dEgwc8S@ZPV6E!L>yvZ<}LIFUE3`-7Tf4jG<- z79#(rCo&5Dau_1gnklcpp$ur{4ngUfLtcsB@qq^*u^)$+t`ef0(sZ`j8@gKn&%Z_{ z0B3}>0gJgQtHe$XpnGdFjIxkqgFvP00x*sfasn^_!U&fEEH7-B zv>(jej4KE#wniRt3}=~OaBoz>tY|GoJqHwnUfk%C$zpZ1)61l8cXETb4&ZJ^rmcU_ z^y$?WE=e_vPfam~PH@x?GQS&r>U~h(B!YAV5Z@zr|H0D;Vr6~#h#sDVfYX;`D&jcj zwnQjz;ITl$g8&PEO00q9l8&6q?kLbu&93!?=Q;$hZ6;tc$OTa~XeR0;T@C$W9Z&IU zPl7)*(w~j&e=&g&9HOp}s72^fG)XF#5B12%Jg~)zP{e|Uq1^hzr8-)Wr2z>yagZ&N z{KDWC1N=e6<`w9E$)7|-0)ZT3i&}SI{ZD&u{ttEAhL4Y3_N}sKCrPrDC9;eyOU9CH zMV9QPjG`G@uKU7J2`Y9V zhe4wN*XXK#EB-pVQc zrDS(;{l12*iGhib5vrd<bj9&NhXTk9tc1WVg%XGZ>EpnUe(Nz*csJS@MAmMzug~@J#z8

?zX|;VWBgyw>0i5BBnM3%QsxHV*+U-=oAfElO3u66*)=)Z z8LP{AP?EmWpJct)YYH4)I07$0R5v9*|IkV-TuND;^Y?9~c@pPoA3`xC3iTk69)~>z zlkV^?gt&~7{^~`p0dJ%RDshA&!M+seSu_Mf=PGyWeboMzUK;VswS@)`po#)aG6CLG z^>}a^Z0e~#243)J(o2^vd`w@rd2ZnyShfR&7YyMC@ay&H(3lJKfTCZWy8$oj@Ky71 zvGdQpFAIRd5X?DC|204Fc{ChnKvU>1cXBxyFd*b+fS~f5GWRQwC4EeWW(^V^eb$O| z+4MXK)Xk~Kl3QL__lsSHWEhFZ11(^jdtH$N82&LRh)peY` zeg+~m~9g9i`30!M(0^KZp-;|b6A3RsNL94XVWSDzbv$eY*|(c{1b z2dS=3+5EneUi$sd4&{7eL%$d1ewikhUw~8wa5(>(+%;asGtZHt6>omLzBo=Zqb2>2 zbfRi%Vw|qUZjIR{Zbvft*-fq0D2F87dqQylMD6_sO~I>79aOnF`ks#2x|X zdjV_cFZS>5n#MUih>)Ef^_JlFsX^Luh&g~Ep2zt7&OFN*DC0K3)I8c($EqP@VPXEi z(I<3E2yTY5i6=A0kNQyKINVZaC6jc;3nnnUcn|zaxPP)MCaU(C{WW9G>888oxM1v{ z*`XP50?+~2Qx=07+$JExH=OBaVv0~l6mfdct9}Ls%i5?}%9XJeHTL;Xv(Vsrf9ck- zMV6=^)HM7Y{86ot?=OHliDsLVwmb!2#Zv0W_A1@>L3B|4*tJr z;s;EQ`oE$YT$$Tm4mPH_d2E+;UXSL~UN;w6g6$a8F%0Bsv)b9gzU(VCwt}wdl$e*S zbWAt<84S)m0|rDy``POOU}$s|MlptQTv37gv`&snt9K4x))Tt_aI9e38;He-NVB#1 z56C)13!KS+>s~y(B00YQvOR`rY^&Y?E7cX23(+#?-wAJjPYL#-U+x26RT^-6%>*3M zXmsxvRuCb+{)DX>Q6#P#MY)!+ak;IpeUQXHf{yAKC7Ql-WgRG7P(CAZ5m;&H--zpTTqchCm(q^4H=Mqa@?TVZ z>(pk%lA+pb6ZRQMhA{xN%z`;>H<;@>?cSht&n~ZCS&i{NIlrAJb#GCf1B^@nLRSLr z7-AO7PaNb_Sk|qp*nNl8zz+u7?^=348%Y{m7*?(FJ(fD23N&1`Gf@Ot^2cYN!>NB+ zd%R(1s^dMtSg@2D4eD<$oAX5TnIF-r0@DYMl7W{v15~CRu`;`-c21*q{~9L#>4egi zCI_!jGZk8%GfKa|27_v3PWA~Iu<^&)sVLtNQEO=loBZ7{)^PQ@`6Lk>kAa7UW?p0l z@aA&`j}Gn4D(=Nhoo5blPWJM<`}4GSgx|roRWmKa`W8sFC%^ckDo45-^W~ZS*(K>W ziq`R~0_~a-YF1+Jq2;GgCu%9v?RWkC>@7ay$EeJ48(g8uQF@ zgPSkUmHv+wqt;t_K$J+h3=ie0vIQ@ayG8q`KxM6P)ZZ`j)n*_0KvR=OvJJT))l&d% zS|Q1G@D2mtX{`W`1)`mkvZD+$Eht(SY~*dMwZSlFhd_tyLq_PVpT6}AydiRV*O$?u z@P|^nKpz^7*pU5@{J-j>nxz+=10c?2E0(H?{!cr3IZuIFgaaak!6>CsB?1l zM1IL0`WwAoV2y!98jXd6=x@C$3d=3?Au3P1yc5{ZX|BihLMe&ZUr1k2{SBGhApS%U znE7-nu{D%E^u93UcgCzFP2SyP*mhCOa+nX)pwSEtSz6hu?H#Eu9)5bZw*J{a5Zw%v z+zX~~KS?$xk*6e6O>tkU;iByloF}xYGPP#+=b(kn_xA0rXTW%IooH-+_vMm?>d~WM zdXMjkbfP`pS7)nxzqFis20Sa)+Sas@$V)fO^vSq2C=#yPl(bq>9AdubH<2*Q8w%35zbOCCG49oXEAyR8`y zhTR7=9P1TWgM{5-#}(D392%cf^0r12Li$uKV%?bg>fnj=j`TS7)%^sUDbcE`s?%-P z*DX5>v?!K8Oma%Wn*{tApJtq306@PJ+$nE?mV*P?bwR*zv)y84lp-g`+G)iQ3oi&X z*d=F?uJ?USygc0HIFR{PVk6U?0tcG$iGvqb?QZw;h3o^Xr>F1yj6NGtniWx(3ZyPk zuuOF&cP|`0{P8WJ%+w}bN5_VVxN#So;Y3MGcT(^D;x)~=GjXD(dS02z&+ z_wqOB!KD&6b(d`yV}WSf%48Qd^3DKTS!RC$!B&1p`|a25vtoV9V8fSwLK?rB#i-Eg z=6}?iI)gbq*z)S11mjIofM)Z;8{iqiGUuS(WO}eo<_YP~ zzt;n)iiumA4gXp|TEG*v9pLyp+3Ap3USRD@kn;QVuT4-5R`a!QHwe~}pg z_|}$n1-@^5lO#q~+aHt?8l@Zp_9|fOh15q7X@@9HvY!I~$?hQAHwD6d0&zEhsl$8r z-}tC2KjZbO$BwdHOwtrZ9xku|S5;LdFs%Z&(rx*34_Xtm9j#gVCzsV{XCXz>`soW% z%82bdB|hT6pF?JcW{PerTHpNJ^6mPp?=d1%-dv%xVukJ?-bL-v70|=l5s^i1L2nk) z<8$F!t6Ff!iRa|xfbYff`Y|Xak0UzTv*~EoX*iWe2XB-n<)#W66}9##vB9lVJ#vKj z{ST6@0)rYv4X02KPOdF$2m>3UQdy$mn$qf6{PUky zC)0|ZTes|z$I_%akR{Pz_>ar5WbniCH~o#q)2o?6sF;bN>1r8jwEFHJyA8mq4P(H(|L8qCt-7{Bv{IbE1R?sR*K-;3j8|66&;8#-m2EFl1qB7I z%590tcl@Oaf25S|ho1-kiiEd2l7UvFL15}X&~;Ptek%rh1g&N5E|ha@EaibFZ)LLg zgwHR4!gnIH*KSb3)C4$b=bn(2!L^#DVeb^}-*X%JahY5YBi7zSSrx zt^OLS>W>$s$S<~Lehv?-PxtRk4bVV48^1WWe{up}MkuQBjn0m91HImh<{vuIdhm7^ zn(0G4*gQjoFi4*p$z(-~S^cj2eEQCT=Y)@@E|t&2K_=+1S3f_v384)Suqv$#v*`2M zw=O&&@u=GBTK|D16*f9Z0myp_epj#%8YKfIQ2;F)^|GA^ajNNiw&ZGMf`<~q?zr^o z>9A6%WOrl>)@5;Y^3f4(lV2*h)_eW8`o)uG`9Tg~kIuibL896f?eS-H;Oi*~A+oH<6HC{#x=YgB#x@6m0W(z)%a?ah6q zmbpZoL$@UEmpb5o`!D(2CzSQD+fU6MY`Nq4~So;CaFk7Wp9%^kkJfuiz|9h>eQm6SJ}m)sGb=hxB5-$yBlr$TyvaL{2X zrq;#MBE4y~iLqDDir<@Ta7`UM+nP=P)U3vJ>*$=<^3GWdL?-qjpeLC z&vuj9Ln-NOiQhvQ3gZ6s9!Wrk8Ia4sYa#r5b=>L8&(z3tqj;Qr2GQuyB5^}cIWC$; zd~tud9Yev_))VE)Y!AT{8Z4Z)Z`TiKvI5{^mG5*-P|_j5hdTSMe#x~I-KTHF#t_4Z zlU*b*V?tX>9nlOFZ->D>6oaZS2mLt9WlbATQs`HCn8JU!1}wyUc(P6{(H2*$qViyR zs6Bo^g{J;8Mr{4gujrE5CA=E%8~#Uz$!1NvpU-;o?V)2~x^nHZYBzPA-aflHqrby! zFHp2z#vYLL`%L>Hx7_~S-J9T-45n5CL zkzDwJBUk$`j)lmM(%m{J5XMLD(fT7 zBfB~;mStbWV#8D0CQXm)jL;ciKn_s_{TT2St0Wel%*p>uC0@8U^Z-+S!M!vkj6HlJ zhCckxojaIumesxB-v#RpG~O)<>uqx(Uu}H%X2ynq6`OO#e{Vz!q_ov@@fd&y!kOx7NAZf}2sZESI zn#e8I@^WSbOL8(@NJ^SdJKLwwfsD_P8~_YX$Upv`D4`w2lT+(Rf|TOtzFOJk7iNDQ zUX@oat2wpEe-{a{!7{v2lk=%p!y;~0CfHSCJ!iuZcs8=$q(y?kpE8{SaeCmNc*?jp zOfpY&{*T!f$!7uYp^gf=e?W%#*I#y^wPkdZ`pUt8QmFc1%LND6I6cR=Qud zFsGTWtn?8KQg26eN>1&S6k>a(t5stf#>yfT3*mS$h1Dac6ntGlj8X2)^k5?OR zks@o~#ZuYIj|RK!1s{22aS5^D?S~JQc@I6~>ylhjejQX6Ao`$haT zBhGAk4UMC~h7`$jl1`!xYVlxzE|7V7k@h12Urfm0PbWV%C$?Th>t^N1Cx87HCO z&zoCad1{V*Vg@zoPv^!EbjeFO(YoOWo*huw-!cg zkW4;mOwcmS>Wc&J%`1)_q9pC6O7iN{5g=xlw; z<&{tueTpA)z=OO~?Yoz-p;L3YtETm_A@)hx3Ww{u_^5Q#ne5W?5MHy8lL%@6|Cs_V@V=Ka@G~|pWW=faw&oU335_HF;$_@c26py0-Ev&gZ%D{|FsVA9T^~4wk_F{ zM_+Fo(ilp06qP7yg<@XKz-~~m_wX_zY2lm2EhM1+3G?w{D?jj4(RedXluIz~1~^*n z13dkQrAR1-C%C}_U-Nv|BYv91Uce%ns zZHZW%=!6lg~v`-Sa47Z9r(!o-DT-2*oYHy)14Qsg0EOr_}iYviQL zUYcm+rl*kst=Ex}Q+Qj2++KLTZ>SOyR5J!WjBED?*bu=fX4yrZ=VyfK7r=@3PjGf^ zyF0<(iE>%WiLh_)@A>=9JTdx3C$9s@r2eh4K1C{#Wsk)$mOt)To_LWqG?-!h?44XV zzCysgcJ6y&Q*4gQBRJ6@bRxrvm88~|AW>g22G0~64*N!bSicgE4K>Sa*mdmy zCE*Vv$K%on13_acR=JZD9^qa?r)DZ*kvt3;_kj%VOXlN#cNQ$J@d2rYq^FZ1LV;i> z*nl&oFVjeNvFBlV@t2zV6~ysE(B)s4@9wH=v}}AZfB9uF z!o*O8{kLsu<`ggEP3)$<@r?(^{3D=$RR>_B#(zv1o|i%&DNL(}kuoaLq{EG8I+DW` zuCMqKu8HGISk62L5Ip7O^Ok*BQY1b&ye2?s@c8yI#)M(BS0q-Zu>hXolc=Nux zu;sgko8a0`m@1!?5%cg&Kh|XSW(JsxRU;Ordkd3pzx~FQNLZ?H$=d(uKPhI~hR)ME zGNwGpz8rJ`WMH?%fP6Kr)Q9|*@~7J3gP-|iqMrCW*_GR^&+#sM^|+AHygDqPpd3x- z-D1@p#MT`ISw@~uwy4c)>NbaNRUdu9SI*v=YfYliSHKWb5D9=dIy&0Ekjr;>*mF0b zJg}xTg%Eep|ANo7Uxzn&ZyGdqEaM@R6qkumu5=x!|haX%*i3C+9;#7_E>+ zf=;SbV%W=3zqn88M*MLQgOEz3B48;*kwY=JWx-$lcyUXQ z?mil#(;qXJ=$Jw)253H+=~+I+lWj4t&M~BFHwq?vL<&!0nBd*Ji8_UQuW2P0D}JCd zIC%+;H}1G$Kr*(Q>=to(FTlsM8b~GZ`^coSOM?aBhs%SnR0faHVJN|RQ`n-TwGCHX z#3?>(@TFRai>YA-1&dwKSt4iNelVql*`s_^a{OqB>PAiiCClUQqigDH27s#p&Wl!s zE6#4pgZXp+n1;#E*YUko*Y?F`w%5Hx;5pj&cB$ z!6iUXbA4*6+$lAI693Ub!NQCB?@N_VC#g;WSuNaG^+_9dV*5TXyj6P;A`ce+^^YP%*`jygY zo>2ehfWWOg;?ON;wO5Wz_gwD;?8GotGZ_j=+sMyTRVk6}n78BOETeet?!(Y&Yonuw zI%MJQ8*qVHoBqUu`|>Jd<$fuO)Phw1+;6#z6BmV8G4jlpfg+T10L^gd6FmgraCb{%M&j-Im+$H^}I z{!c#R1X;{U!2X`60E$V^p)f`FWk#K&NDf|;R(R{UyEOdxYbW8O0U50{FSpfK%<4pT zjiC3+g_J;Tb-f+-Kn3HV$9RpD1p$KcC|Pl0+dmiVDZAFS2RV0uMyh$8qz=xCO zn+$MS6eVa1XVrb97E{Cxkka+_8P>D9T=Ys7HdQ@ogW^R$3Lj_R%YBvUS!gTYu@(;Z z56SmmnZ#!aYDwSmfUtMmm75p0H;pk})nX=86wRhD4`?s}+w|trKMJ!$a?Yxtp~Uz# zsb?w{yl|2hFr_|4kmM1Kdq8{5c>~OvVQJrvM*LfNdHeK%T*7UW$pU{}=zC4mTqm3H z2v`Rc1|UEWEbg!qLz$|X3r#3u zZVa#|V|Z8baAunmKIj4}luU?f3{pW}Dswt(8pzfX6T;WPq=%Vk%uW>>rxRfaP!=RY z&C<36;4Y#>{T+GQcJoqGWsc<|(bN0X;jmI!A*51sRIdR5-PS2G03N(G)OR`~yGK-=Ij<1w-&(K)2{<3|I|a&LkXnFW!Ya zT5c;3AmeGE!=h1Ffvr5v`*(>7G;RT1F*M@1ZaB2ZuLv_G&%!`MjT`>9@#}8nfBVZG zMg!U5q*~r1pWdjHd0rGNjMB1^Z?WBMqQ7IxO!K95e}2XyxlfI}MV2Y%*ZB;I{+*2h zWYX%{?bWj=!`CU;Y*)W`Gq=6d-Hn~ZHa^5yU49FdE{A!xGK22w4|h=k-p$o$v8Zp& zN^Eq%9_a=;X%HA>zr;`GioT4>^@~iT9)O!SS^2+eG1If-_c1hshfe#PU6+I@PlkAZ z9b1raCw@|NYao%5mF@!3;~e12m?)&UfPn0ul#0j1vDgN+SY_M1$yd-LCpO;^zK~XJ ze;$L`=WmMDvo$S{tsnX`6&d+_b;JoON!wYaN20g79kBtL!!E*xsg9COfDD=E9TT{z zU+5cO8@$^olL5-*IB}Jm*nC&?lvBx9>TK+q?>IR+UhLgpR!~yH06FzRzVL-022or$ zxEptOw!>Uh2>?l?!b0ZkJ)sr)HjN#d>3LX8ib@YPKy#wU)}skr^S%QaERZPPefW?B zv0!)G5?vHA{1cl&QL@U-tpz&|q{UpK@XU6NwQP5gpO;~fxi^IP-JV<#Enks=>Z7n7b&MYIsT z?7Idq(;+-XmaY(1b5n}Hpr#juL5X5#d;!r?5m@;AuZ`LiFo6~j4u^a{jr^o2Yf@tA zm{d52b4oTQB#RY|*u@}W1P?2mO5i(nOTy{l*_g(d2IVmD9_3-_rfAG36HLx&s4hSB9 zKg{Vooftg-Mts#vL}V#qt!3&JY^6gP975y-PzL5$;p{$2H2hpVo6;yjb7wbYdS(;z zeW4pFJHF`yMLQc3rWn8LTy6_6i{pf$?cis;{NsSoHzyhggM?SNE;S{=^aauKVWaoz z#;aX14|Pr3owD52pT>)4hnPs-)x!Z*iQTYiiFF5TW8P4; z__Y?fy+$E8biIGA=vfpd?Vuf+dqS7#2}2^_HEl~3=F@1#0~~ckafdEtuqs!Bz?6l3 zavuG@yWq{UTLwm5Dh4DUelmiq(HH_5@#;?88CeU*;{H>+YCF2(HZ;Vouw*vO2cN<8 z5PoK4HY_Lu77@?ZG{` z*5Ue?W>pI}e#YJ}$_T*R$?RuqH=LVqf0;4uRd`k#3y%%nrb-wiHZq?+4w4&e`k$_G zr9neag+5KS>74GqCr9zY*AFyAEl*2|bSR(lf=M{4IndC3w-oeva<@oysT)Fs$ukmT zSc!3!$^mI2Z-eo<<;%P1XVr~#@|iG5e(3&@fE;T0;P0@n4`e7(W5xUc*>*dLf|#JUlpUjcK~4an?&DfaE- z*X~rom zol5^fJr?xdEq(@OkA|6^5Go86vkXeCZv;%h(fJvE79T;&#^JM+!nCLS_wWOgsK#-7 zh@ytv!rgj>Ryzh1s8oxEu766NS+1%hX zmM!^=Tg1cZ;xEGD;s_NB>?iB)o~CVeRxAtQX{o*qzt;@xRdmB7)ZR`;537A!4Si!b ze&YN`ybC-P+D`aSdqHRIrl$jOLCTbeUhl0N9ShDL>S4%8_ITL;e;Fi9RseX=dGr;~ z+faB%>?@cf13(R29o~1ca28i3d~(iBg|8X%8A8=t#WeB%BivB>f8EpD#Vh(4cLn|k z+(v-^?Iuj4L|PJ;AQz(P6{I{-j9C6)aY@O|$G9R*m?9FgQUri(9{?8h@Zeex^(mvU zwfD?bap-Z}dfsy_1}Hn(_?`x*^E(pox{rRiNQbOw>DuK^=rd1F@o2Ih4^nG>2TbmQ#K9OoiQvSrUrH(LFX(h)X!T=h&{)E$V+3(B9@cI&pw`e@r1GQ z^W_(S7m<&>K@Bv5c#v%8>fsm3qxGH+A58RZ|M%Fa?6!liF;P2_4|pe>Fg<}C$BO_U z(dpVLHB~qyqyVsr6{j=9TM-diZHKKqIyn*S?%f zy*Fn?x+uY${vozKPZfzDb4WA#|2{6>|ny!OT8Yhn;KFU5gHO7fDip+LhzaRG zlK%Ag#ti4L2U8o2x=ugkjIZ&Cjc_!0je2hv?4x;5ny8+fDrHJO_7dJBH;mD-FaWA& z@?^TQ_@Vzq$;(kL+#&RvM(7WYK)b!;a(Q0ow?M;jhrnfPEg6xX?Y1TdD3eE zAHg7(y;;tec8Ok{rg`$@31KkRUzK~69X)gIT|es-I^E($;y@FVd;R(uhFIlG|KVLQ zRxUkZ_h?ovMMS20x7Xm5Yra1Yy>61wQ4VMvPOZXBo&&y>5uYK9B7FfZy%oK6-gTwX&+M8j;-Ns~JTA3gvHNGHD=kfxXz4o&8>u(j>RALn?j zjOoUZ%ZczTpB$hjLLS`ydv`klkXRas)V?mc9!w>(>J~PLMWA1#6AN9*W~|5dXZTvx zK zLHAGAgR^SGSvmgF>^`tpMUol@92(fk&(i9jS)PAtj{8&qVzY*$r2xaat8 zoE%r`^9g8Eo#>4~uUFnN*OH@6h_on}bp`7XPDse~7`PxP{euse#eYF;6OF(f`SvPo zSN8t$qii%*LWc4gERacQB2oX(i%5{bpDOJIeMa~Ob{&#*$BVXZK>BzDpeo36oddEm z8c<+#-zYqymR^v2Cls?H!wq#@xT|nC;tC!$~o)dKjY$g34uIz`cO5Lmbf5vGk7?S;I-(+~_3kafHNSe>C-iU}jGY>+_u zwi^)Vb1YE_>404v|*QpuFO_`23Sw8U|ZOhH&r$5wTZ;~)pf_P6cg+w9rSi4sp~hXKPj+*S>&uZ z`()U1VsTDc?n8?v%fL!#+K}@nbq=&>3fF=LULD-Si^9g97?JKV&&DhP{i3b-Y<&q@ zjNMVW`dweHzEfg*R(%^vPfi$7#LTJ#?uf4q%VR3gOn9qe+0bqr*}7BKm-#Y&+17&s z>|3*Z!CoXIk76c}2nsvfBSZLkxnuMttj&i%>@T1jS582M0n|4?#|9(z;P}dMYJlP- z@y5JtHQV|qE$4q>A}Lq|0mg{4rnPxx(EXgmx#C^i`>l=TItuAofX|6mj{&WXNAgra z-JC8CIjLMzEMu{)wkWW`lNlgQZOn!O#ln@(5IHVg1Qv3+|KmJuDT(pjI|o;bAt>yt z955>{X&?_zC>-`jHL?}zC}sa?gIU+%$V^Wd+q^o5^9+D#$#HkE$NdX%MP>5(m;2cX z1WCYx;NaXaT1)6#{G`F&jIf;( zQdL|4HOJq!$2n$zT{F`nR2zM5KsDw<<%{q4eX)|yzyxn>N9jp&L$r`E9F!xE>~_p7 z(R9%s1lc&GrIf5g1)X}xMpe<>)EKca+Eh))EcFk1%%9f|ySj_FbC+GceePnMst*v+7JMY0_)< zknS{Tv43Ef;g1;JxiCdVV>uA>*8E=)VcZ3PZuEIgENOq*A`~Ny0i8j@Fuy{P54eo- zQK>mcTSZf2Qnh5hF@S^~x$TpFVFB$cxmy>JTJpxJ_lmP5%$(IR?W`HN^cgdxD1860cZ&iJa1IJ;wiGZUZQ9fJ$Irc8noR$0lV@g zq|%8+iA?d4em)+Yy=x&SdLS|d! zZkeJ$H(<10yEnb`@bW+hRGbxpwjR}aq%Pq2{_c47f7mCZrjrkn3TUEGfC6@c474u(6Q_Q&LM%Wd+DwP>v^gd5Ka5sM`4@jXdZ&t&cw5M7Xo222>> z7W*=34}Lq^-Sr|l$9(l~I% z2HlA^Z-*dR6{Zp$8$la%3a$x3WT99qA1TpBi2*6Qf&P&gn*n0p+6A_5@;BA%%^)%v zEVBU=WM^M~#fuJYGysK^g{^7ge!`z4=n;8-X6EzuOM@INlI55Q3 z1uWKlLGW&tOGq}$_0O|WB0@I4e)AM*v9$H*7X#VgooM${2A?rxGCVXCjUpNFiH(K3 z0SEUrDujzRI=uZ6+q$uyNVotPIXC_`un5rT^A)^Ct~hZx>?KA+CaofG_wbwM7P7Vd z@L3KwVLTZz?_>nE6CAAWHXNTDEER$&iZHTgg}n!i@3W`Zw|*plNK9yEMbGZYkf%`h zVMM{Y{#7L(NiCA)pIVpGLtk`Jt|N`!QhUE?8u6e4bxmrjJ=oR^vjG&RfF?TwGkPa$NGPZR_JRq!EK7 zM~}J#1sAXe7^{^&2z<#I`PTqIp5)Zk0L8pHXm)mcT>8$&y{d}d&OwhSx7_%l*uaZ` z`u7ad_PhuXj$OcVia@w_t~ejh6H|;V4ctI^8~!+P*xf&7j&DocG% zRngMFH_^wg-T+_*zKuaViCM;!cJMcF89!c~Wk+y6WCxfJ0A4HqfN2TbkEoX9#$ADVD0Wi686PkKf+55ieL;1!)^swG zI*cwA46)#vU7uo#YG!7ZzIadYg?cjd0PydSqO5c1mki(b`lON6sM?Uf!2x^gH{h8GYOFaiS+D}eOTbTtAwURi*)4WydQPNa$fEhWEdpCJ(r{D7wax=i&m*hqD!psaP*b*ua7DHx@@f_}kX?S>U85 zQi~lt_S>ACulI4}$m^OGxCA2bsEI;6{CUAcrieX0zEQ~J(;y`@N;d9SR_yxyJaU4(QVypfz2HDNp8?$To1eVBhjZ3TRrHaS4QvFx7+${hckd zABg(!dx*9zXFUGCQrpm7E8@wjrP-DdcrN?4+yhfsyn^6racN6jb%3`ATWzvv==bH416frwIYx<1fQtv${vroL#%^G| zlvwM*4h_=u2F$a(*tPZR$tA1mNrUhIbO7EBYo7~6%{*YkNicIZ4u6>RAAnGR12-=I z*zAM{gMnTRek)rWIehrerpat&{lH6*yDkvKYDx$gF7Pu(0Zj*JC+?tJ={r#H{b+KL z3Hzh!0B9H}w^6=p)n81W0dGk<=wpDELb2TEG!a~Me*1GckkVhEp)=@Rq5#qpjF-hqK# zpu$X=+!pv@v%>0?RxsJK1Tfp^*cdEw5!GS^xl94(IP8eBoz=;=$4A2ipE)|DJ(K>PF22Cz z|2zdm@e?DRXFKthu#W+VPmI=mbKicm-Jo=gV6mI%*u!6+e)^(+pacVd!HUTL`^W$F c5n#6V+2E3QUCP-_!oXjqhL+fBeb?y!4=mxZ0ssI2 literal 0 HcmV?d00001 diff --git a/src/api/AxiosService.ts b/src/api/AxiosService.ts new file mode 100644 index 0000000..4833ea4 --- /dev/null +++ b/src/api/AxiosService.ts @@ -0,0 +1,98 @@ +import axios from "axios"; + +// TODO: Add calls for notifications on varius states of the request + +export const setupAxiosInterceptors = () => { + axios.interceptors.request.use( + (config) => { + // Do something before request is sent + return Promise.resolve(config); + }, + (error) => { + // Do something with request error + return Promise.reject(error); + }, + ); + axios.interceptors.response.use( + (response) => { + // Do something with response data + const status = response.status; + if (status === 200 && response.data) { + console.error(response.data); + } + // Check if the status code is 401 (requires user authentication) + if (status === 401) { + console.error(response.data); + } + + return Promise.resolve(response); + }, + (error) => { + // Do something with response error + return Promise.reject(error); + }, + ); +}; + +const handleAxiosError = (error: any, callbackFunction = null) => { + if (error.message == "canceled") { + callbackFunction && console.debug(callbackFunction); + console.debug("Request Aborted"); + } else if (error.response) { + // Request made and server responded + console.log(error.response.data); + console.log(error.response.status); + console.log(error.response.headers); + } else if (error.request) { + // The request was made but no response was received + console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log("Error", error.message); + } + console.log(error.config); +}; + +export const axiosGet = async (url: string) => { + return axios + .get(url) + .then((response) => { + return response.data; + }) + .catch((error) => { + handleAxiosError(error); + }); +}; + +export const axiosPost = async (url: string, data: any) => { + return axios + .post(url, data) + .then((response) => { + return response.data; + }) + .catch((error) => { + handleAxiosError(error); + }); +}; + +export const axiosPut = async (url: string, data: any) => { + return axios + .put(url, data) + .then((response) => { + return response.data; + }) + .catch((error) => { + handleAxiosError(error); + }); +}; + +export const axiosDelete = async (url: string) => { + return axios + .delete(url) + .then((response) => { + return response.data; + }) + .catch((error) => { + handleAxiosError(error); + }); +}; diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx new file mode 100644 index 0000000..29a993f --- /dev/null +++ b/src/components/Breadcrumbs.tsx @@ -0,0 +1,72 @@ +import { Link, useLocation, useParams } from "react-router-dom"; +import { findElementInFlatRoutes } from "../utils/RoutingTableUtils"; +import { capitalizeFirstLetter } from "../utils/StringTransformationUtils"; + +const BREADCRUMB_BAR_CLASSES = + "text-sm breadcrumbs max-w pl-6 shadow-neutral-focus shadow-sm sticky top-2 z-10 bg-neutral transition-all rounded-md m-2 w-auto"; + +const Breadcrumbs = () => { + const location = useLocation(); + const pathnames = location.pathname.split("/").filter((x) => x); + const pathParams = useParams(); + + const hideBreadcrumbBar = (path: string) => { + // if (Object.keys(pathParams).length !== 0) return true; + const _route = findElementInFlatRoutes(path); + return _route?.disableBreadcrumbBar || false; + }; + + const findRouteNameByPath = (path: string) => { + const _route = findElementInFlatRoutes(path); + return _route?.name || path; + }; + + const renderLinkBreadcrumb = ( + value: string, + index: number, + pathnames: string[], + ) => { + if (value === pathParams.id) return null; + const to = `/${pathnames.slice(0, index + 1).join("/")}`; + const routeName = capitalizeFirstLetter(findRouteNameByPath(value)); + return ( +

  • + {routeName} +
  • + ); + }; + + const renderTextBreadcrumb = (value: string) => { + if (value === pathParams.id) return null; + const routeName = capitalizeFirstLetter(findRouteNameByPath(value)); + return ( +
  • + {routeName} +
  • + ); + }; + + if (hideBreadcrumbBar(location.pathname)) { + return ( +
    {pathnames[0].toUpperCase()}
    + ); + } + + return ( +
    +
      +
    • + Home +
    • + {pathnames.map((value, index) => { + const isLast = index === pathnames.length - 1; + return isLast + ? renderTextBreadcrumb(value) + : renderLinkBreadcrumb(value, index, pathnames); + })} +
    +
    + ); +}; + +export default Breadcrumbs; diff --git a/src/components/Card.tsx b/src/components/Card.tsx new file mode 100644 index 0000000..8eb8223 --- /dev/null +++ b/src/components/Card.tsx @@ -0,0 +1,35 @@ +import { Link } from "react-router-dom"; + +interface CardProps { + title: string; + description: string; + bgImage?: string; + link: string; +} +/** + * Card component + * @param {string} title - Card title + * @param {string} description - Card description + * @param {string} bgImage - Background image + * @param {string} link - Link to open on button click + */ +export function Card({ title, description, bgImage, link }: CardProps) { + return ( +
    + {!!bgImage && ( +
    + {title +
    + )} +
    +

    {title}

    +

    {description}

    +
    + + Go + +
    +
    +
    + ); +} diff --git a/src/components/CardGrid.tsx b/src/components/CardGrid.tsx new file mode 100644 index 0000000..9956d7a --- /dev/null +++ b/src/components/CardGrid.tsx @@ -0,0 +1,13 @@ +import { ReactNode } from "react"; + +const CardGrid = ({ children }: { children: ReactNode }) => { + return ( +
    +
    + {children} +
    +
    + ); +}; + +export default CardGrid; diff --git a/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/src/components/ConfirmationDialog/ConfirmationDialog.tsx new file mode 100644 index 0000000..2afad34 --- /dev/null +++ b/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -0,0 +1,39 @@ +import Modal from "../Modal"; +import useConfirmationDialog from "./useConfirmationDialog"; + +/** + * @description ConfirmationDialog component displays a confirmation dialog/modal with a message and two buttons: Yes and No. + * Actions of buttons are definded via hook. + * State of dialog is based on react context. + * @returns ConfirmationDialog component + */ +const ConfirmationDialog = () => { + const { + isConfirmationDialogVisible, + handleConfirm, + handleCancel, + customModalBoxStyles, + customModalStyles, + } = useConfirmationDialog(); + + return ( + <> + {isConfirmationDialogVisible && ( + + + + + )} + + ); +}; + +export default ConfirmationDialog; diff --git a/src/components/ConfirmationDialog/ConfirmationDialogProvider.tsx b/src/components/ConfirmationDialog/ConfirmationDialogProvider.tsx new file mode 100644 index 0000000..7b76f23 --- /dev/null +++ b/src/components/ConfirmationDialog/ConfirmationDialogProvider.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { ConfirmationDialogContext } from "../../contexts/ConfirmationDialogContext"; + +export const ConfirmationDialogProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [isConfirmationDialogVisible, setIsConfirmationDialogVisible] = + useState(false); + const [customModalBoxStyles, setCustomModalBoxStyles] = useState(""); + const [customModalStyles, setCustomModalStyles] = useState(""); + + const showDialog = () => { + setIsConfirmationDialogVisible(true); + }; + + const hideDialog = () => { + setIsConfirmationDialogVisible(false); + }; + + const handleConfirm = () => { + // Logic for when the user confirms + // You can call external functions or dispatch actions here + hideDialog(); + }; + + const handleCancel = () => { + // Logic for when the user cancels + hideDialog(); + }; + + return ( + + {children} + + ); +}; + +export default ConfirmationDialogProvider; diff --git a/src/components/ConfirmationDialog/useConfirmationDialog.ts b/src/components/ConfirmationDialog/useConfirmationDialog.ts new file mode 100644 index 0000000..4363baf --- /dev/null +++ b/src/components/ConfirmationDialog/useConfirmationDialog.ts @@ -0,0 +1,23 @@ +import { useContext } from "react"; +import { + ConfirmationDialogContext, + ConfirmationDialogContextType, +} from "../../contexts/ConfirmationDialogContext"; + +/** + * @description Hook to manage the confirmation dialog, only passes the controls to specyfic dialog component + * @param onConfirm Function to be called when the user confirms the action + * @param onCancel Function to be called when the user cancels the action + * @returns Hook controls + */ +export const useConfirmationDialog = (): ConfirmationDialogContextType => { + const context = useContext(ConfirmationDialogContext); + if (!context) { + throw new Error( + "useConfirmationDialog must be used within a ConfirmationDialogProvider", + ); + } + return context; +}; + +export default useConfirmationDialog; diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx new file mode 100644 index 0000000..aed735a --- /dev/null +++ b/src/components/Loader.tsx @@ -0,0 +1,17 @@ +function Loader() { + return ( + + ); +} + +export default Loader; diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..7802a2a --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,109 @@ +import { useCallback, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import useConfirmationDialog from "./ConfirmationDialog/useConfirmationDialog"; +import { Cross1Icon } from "@radix-ui/react-icons"; + +interface ModalProps { + modalId: string; + title?: string; + subTitle?: string; + content?: string; + navigatePathOnClose?: string; + children?: React.ReactNode; + customModalStyles?: string; + customModalBoxStyles?: string; +} +/** + * @description A modal component that can be used to display a fairly custom modal/dialog + * @param modalId The id of the modal + * @param title The title of the modal + * @param subTitle The subtitle of the modal + * @param content The content of the modal + * @param navigatePathOnClose The path to navigate to when the modal is closed + * @param children The children of the modal + * @param customModalStyles Custom styles for the modal + * @param customModalBoxStyles Custom styles for the modal box + * @returns A modal component + */ +export const Modal = ({ + modalId, + navigatePathOnClose, + title, + subTitle, + content, + children, + customModalStyles, + customModalBoxStyles, +}: ModalProps) => { + const navigate = useNavigate(); + const { isConfirmationDialogVisible, hideDialog } = useConfirmationDialog(); + + const handleClose = useCallback(() => { + if (navigatePathOnClose) navigate(navigatePathOnClose); + if (isConfirmationDialogVisible) + (document.getElementById(modalId) as HTMLDialogElement).close(); + hideDialog(); + }, [ + hideDialog, + isConfirmationDialogVisible, + modalId, + navigate, + navigatePathOnClose, + ]); + + const handleKeyPress = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Escape") { + handleClose(); + } + }, + [handleClose], + ); + // Handle modal open + useEffect(() => { + if (!modalId) return; + (document.getElementById(modalId) as HTMLDialogElement).showModal(); + document.addEventListener("keydown", handleKeyPress); + // Handle modal close + return () => { + document.removeEventListener("keydown", handleKeyPress); + handleClose; + }; + }, [handleClose, handleKeyPress, modalId]); + + const composeModalStyles = useCallback(() => { + const _baseStyles = "modal modal-bottom sm:modal-middle w-screen z-50"; + if (customModalStyles) return _baseStyles + " " + customModalStyles; + return _baseStyles; + }, [customModalStyles]); + const comopseModalBoxStyles = useCallback(() => { + const _baseStyles = "modal-box"; + if (customModalBoxStyles) return _baseStyles + " " + customModalBoxStyles; + return _baseStyles; + }, [customModalBoxStyles]); + + return ( + +
    +

    {title ?? "Hello!"}

    +

    + {subTitle ?? "Press ESC key or click the button below to close"} +

    +

    {content}

    +
    +
    + + {children} +
    +
    +
    +
    + ); +}; + +export default Modal; diff --git a/src/components/NavigationTree.tsx b/src/components/NavigationTree.tsx new file mode 100644 index 0000000..89d45ce --- /dev/null +++ b/src/components/NavigationTree.tsx @@ -0,0 +1,79 @@ +import { CustomRouteObject } from "../configure"; +import { Link, useLocation } from "react-router-dom"; +import { + clearMultiplePathSlashes, + trimPathOfParameters, +} from "../utils/StringTransformationUtils"; + +/** + * @returns Navigation tree elements, require to be used like in example below + * @example + * ```tsx + *
      + * + *
    + * ``` + */ +function NavigationTree(props: { + routes: CustomRouteObject[]; +}): React.JSX.Element { + const locationHook = useLocation(); // Used to highlight active link in navigation tree + + const GenerateNavigationEntries = ( + routes: CustomRouteObject[], + parentPath?: string, + ): React.ReactNode => { + return ( + routes.map((route) => { + // Prepare path for links + let combinedPath = undefined; + if (parentPath !== undefined && route.path !== undefined) + combinedPath = trimPathOfParameters( + clearMultiplePathSlashes(`/${parentPath}/${route.path}`), + ); + else combinedPath = route.path; + // Does it have children and enabled? Make entry with `/{parent.path}/{route.path}` + if (route.children && !route.additionalProps.disableInNavbar) { + return ( +
      +
    • + + {route.additionalProps.name} + + {route.children ? ( +
        {GenerateNavigationEntries(route.children, combinedPath)}
      + ) : null} +
    • +
    + ); + } + // Does it have children and not visible? Skip this entry and call this function for children passing path. + else if (route.children && route.additionalProps.disableInNavbar) { + return GenerateNavigationEntries(route.children, combinedPath); + } else if (route.additionalProps.disableInNavbar) { + return null; + } + // Make entry with `/{route.path}` + else { + return ( +
  • + + {route.additionalProps.name} + +
  • + ); + } + }) || <>empty navigation tree + ); + }; + + return
    {GenerateNavigationEntries(props.routes)}
    ; +} + +export default NavigationTree; diff --git a/src/components/Notification/Notification.tsx b/src/components/Notification/Notification.tsx new file mode 100644 index 0000000..d5048e5 --- /dev/null +++ b/src/components/Notification/Notification.tsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from "react"; +import { INotification, NotificationStatus } from "./NotificationType"; + +enum NotificationColorsClasses { + "info" = "alert alert-info", + "success" = "alert alert-success", + "warning" = "alert alert-warning", + "error" = "alert alert-error", +} + +const selectIcon = (status: NotificationStatus) => { + switch (status) { + case "error": + return ( + + + + ); + case "success": + return ( + + + + ); + break; + case "info": + return ( + + + + ); + break; + case "warning": + return ( + + + + ); + break; + default: + return null; + break; + } +}; + +interface NotificationProps { + notification: INotification; + removeNotification: (id: number) => void; +} + +function Notification({ notification, removeNotification }: NotificationProps) { + const [counter, setCounter] = useState( + notification.duration, + ); + const isCloseable = !!!notification.duration; + + useEffect(() => { + // Skip if counter is undefined + if (counter === undefined) return; + const intervalId = setInterval(() => { + setCounter(counter - 1); + if (counter === 0) { + removeNotification(notification.id); + } + }, 1000); + return () => { + clearInterval(intervalId); + }; + }, [counter]); + return ( +
    + {selectIcon(notification.status)} + {notification.message} + {isCloseable ? ( + + ) : null} + {isCloseable ? null : ( +
    + + + +
    + )} +
    + ); +} + +export default Notification; diff --git a/src/components/Notification/NotificationProvider.tsx b/src/components/Notification/NotificationProvider.tsx new file mode 100644 index 0000000..c48ee17 --- /dev/null +++ b/src/components/Notification/NotificationProvider.tsx @@ -0,0 +1,37 @@ +import React, { useState } from "react"; +import { INotification } from "./NotificationType"; + +interface INotificationStore { + notifications: INotification[] | []; + setNotifications: React.Dispatch>; + duration?: number; +} +// Store for notifications +export const NotificationContext = React.createContext({ + notifications: [], + setNotifications: () => {}, + duration: undefined, +}); + +// Just store for notifications, logic is in useNotification hook +export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + // Queue for notifications + const [notifications, setNotifications] = useState | []>( + [], + ); + NotificationContext.displayName = "Notifications"; + return ( + + {children} + + ); +}; + +export default NotificationProvider; diff --git a/src/components/Notification/NotificationStack.tsx b/src/components/Notification/NotificationStack.tsx new file mode 100644 index 0000000..cf0e9c6 --- /dev/null +++ b/src/components/Notification/NotificationStack.tsx @@ -0,0 +1,25 @@ +import Notification from "./Notification"; +import { useNotification } from "./useNotification"; +// ----------------------------------------------------------- +// Component that renders notification stack +// ----------------------------------------------------------- +function NotificationStack() { + const { notifications, removeNotification } = useNotification(); + + // It takes notification array from hook and renders it + const renderNotifications = () => { + return notifications.map((notification) => { + return ( + + ); + }); + }; + + return
    {renderNotifications()}
    ; +} + +export default NotificationStack; diff --git a/src/components/Notification/NotificationType.ts b/src/components/Notification/NotificationType.ts new file mode 100644 index 0000000..4064dcf --- /dev/null +++ b/src/components/Notification/NotificationType.ts @@ -0,0 +1,9 @@ +export type NotificationStatus = "success" | "error" | "warning" | "info"; +export interface INotificationBase { + message: string; + status: NotificationStatus; + duration?: number; +} +export interface INotification extends INotificationBase { + id: number; +} diff --git a/src/components/Notification/README.md b/src/components/Notification/README.md new file mode 100644 index 0000000..5fefc38 --- /dev/null +++ b/src/components/Notification/README.md @@ -0,0 +1,2 @@ + # Notification + I wrote own notification module but its little bit laggy and buggy so I don't use it. diff --git a/src/components/Notification/useNotification.ts b/src/components/Notification/useNotification.ts new file mode 100644 index 0000000..8d1ee63 --- /dev/null +++ b/src/components/Notification/useNotification.ts @@ -0,0 +1,34 @@ +import { useContext } from "react"; +import { NotificationContext } from "./NotificationProvider"; +import { INotification, NotificationStatus } from "./NotificationType"; +// ----------------------------------------------------------- +// Hook for adding notifications to the queue in local storage +// ----------------------------------------------------------- +export function useNotification() { + const { notifications, setNotifications } = useContext(NotificationContext); + + const addNotificationToQueue = ( + message: string, + status: NotificationStatus, + duration?: number, + ) => { + const id = notifications.length + 1; + const notification: INotification = { id, message, status, duration }; + setNotifications([...notifications, notification]); + }; + const removeNotificationFromQueue = (id: number) => { + setNotifications( + notifications.filter((notification) => notification.id !== id), + ); + }; + const clearNotificationsQueue = () => { + setNotifications([]); + }; + + return { + notifications: notifications, + addNotification: addNotificationToQueue, + removeNotification: removeNotificationFromQueue, + clearNotifications: clearNotificationsQueue, + }; +} diff --git a/src/configure.tsx b/src/configure.tsx new file mode 100644 index 0000000..ac03081 --- /dev/null +++ b/src/configure.tsx @@ -0,0 +1,191 @@ +/* eslint-disable react-refresh/only-export-components */ +import { Suspense, lazy } from "react"; +import { RouteObject } from "react-router-dom"; +import Loader from "./components/Loader"; +import AboutPage from "./features/About/AboutPage"; +import PersonManager from "./features/Administration/PersonManager"; +import App from "./features/App/App"; +import LoginPage from "./features/Login/LoginPage"; +import { flatternRoutingTable } from "./utils/RoutingTableUtils"; +import AssetManagement from "./features/Administration/AssetManagement"; +import Debugger from "./features/Debugger"; +//---- +const AdministrationLazy = lazy( + () => import("./features/Administration/Administration"), +); +const HomePageLazy = lazy(() => import("./features/Home/HomePage")); +const MaintenanceLazy = lazy( + () => import("./features/Maintenance/Maintenance"), +); + +export const viteEnv = import.meta.env; + +// Based on https://reactrouter.com/en/main/start/overview#nested-routes with additional props +export const navigation: CustomRouteObject[] = [ + // root page, mainframe for all pages, skipped in navigation tree generation (inly childs are used) + { + path: "/", + element: , + additionalProps: { + name: "Root", // used for breadcrumbs and navigation tree + disableRedirect: false, //entry will not be included in the react-router redirect list, will cut out childs + disableInNavbar: true, //entry will not be rendered in the navbar + disableBreadcrumbBar: false, //entry will be directly under root in routing table + }, + children: [ + // home page + { + path: "/", + index: true, //if true will be used as a default route for parent, dont declare path if use this + element: ( + }> + + + ), + additionalProps: { + name: "Home", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + { + path: "maintenance", + element: ( + }> + + + ), + additionalProps: { + name: "Maintenance", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + { + path: "administration", + element: ( + }> + + + ), + additionalProps: { + name: "Administration", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + children: [ + { + path: "asset_management/:state?/:id?", + element: , + additionalProps: { + name: "Asset Management", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + { + path: "person_management", + element: , + additionalProps: { + name: "Person Management", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + ], + }, + // about page + { + path: "about", + element: , + additionalProps: { + name: "About", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + children: [ + { + path: "child", + element:
    DUPA CHILD
    , + additionalProps: { + name: "About Child", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + ], + }, + // login page + { + path: "login", + element: , + additionalProps: { + name: "Login", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }, + ], + }, +]; +// --- + +if (viteEnv.DEV) { + // Add Test page + // Test page is outside of mainframe, will be rendered in root (withius App where is entire navigation frame) + navigation.push({ + path: "/Test", + element:
    Test
    , + additionalProps: { + name: "Test", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }); + // Add debugger page + navigation[0].children?.push({ + path: "debug_variables", + element: , + additionalProps: { + name: "Debug Variables", + disableRedirect: false, + disableInNavbar: false, + disableBreadcrumbBar: false, + }, + }); +} + +// --- +//Custom Route Object for handling custom behaviours on app navigation +export interface RouteObjectAdditionalProps { + name: string; + disableRedirect: boolean; + disableInNavbar: boolean; + disableBreadcrumbBar: boolean; +} +export interface CustomRouteObject extends Omit { + additionalProps: RouteObjectAdditionalProps; + children?: CustomRouteObject[]; +} +//---- +//Main configuration, static data, mostly used here +export const main = { + program_name: viteEnv.VITE_APP_NAME, + program_version: "1.0.0", +}; +//About page configuration +export const about = { + program_description: `This is a ${main.program_name} for .`, + program_authors: [{ name: "Author Name", email: "email@example.com" }], +}; +//---- +export const flatRoutes = flatternRoutingTable(navigation); diff --git a/src/contexts/ConfirmationDialogContext.tsx b/src/contexts/ConfirmationDialogContext.tsx new file mode 100644 index 0000000..5bdbb68 --- /dev/null +++ b/src/contexts/ConfirmationDialogContext.tsx @@ -0,0 +1,19 @@ +import { createContext } from "react"; + +// Define the shape of the context +export type ConfirmationDialogContextType = { + isConfirmationDialogVisible: boolean; + showDialog: () => void; + hideDialog: () => void; + handleConfirm: () => void; + handleCancel: () => void; + customModalStyles: string; + customModalBoxStyles: string; + setCustomModalBoxStyles: React.Dispatch>; + setCustomModalStyles: React.Dispatch>; +}; +// Create the context with default values +export const ConfirmationDialogContext = createContext< + ConfirmationDialogContextType | undefined +>(undefined); +ConfirmationDialogContext.displayName = "ConfirmationDialog"; diff --git a/src/features/About/AboutPage.tsx b/src/features/About/AboutPage.tsx new file mode 100644 index 0000000..5ecd399 --- /dev/null +++ b/src/features/About/AboutPage.tsx @@ -0,0 +1,32 @@ +import { about } from "../../configure"; + +/** Here is located about page where you can find information about this application: + * - short description of this application + * - how to use it + * - information about persons responsible for maintaining this application + */ +function AboutPage() { + return ( +
    + ); +} + +export default AboutPage; diff --git a/src/features/App/App.tsx b/src/features/App/App.tsx new file mode 100644 index 0000000..7c37e90 --- /dev/null +++ b/src/features/App/App.tsx @@ -0,0 +1,108 @@ +import { HamburgerMenuIcon, SunIcon } from "@radix-ui/react-icons"; +import { useEffect, useState } from "react"; +import { Outlet } from "react-router-dom"; +import { themes } from "../../../daisyui.config.js"; +import { main, navigation } from "../../configure.tsx"; +import NavigationTree from "../../components/NavigationTree.tsx"; +import Breadcrumbs from "../../components/Breadcrumbs.tsx"; +import ConfirmationDialog from "../../components/ConfirmationDialog/ConfirmationDialog.tsx"; + +/** Here is located global wrapper for entire application, here you canfind: + * - Drawer - contains navigation buttons + * - Navbar - contains hamburger menu and theme selector + * - Outlet - contains active page content + * - App theme controll + */ +function App() { + const [theme, setTheme] = useState("adient"); + const [openDrawer, setOpenDrawer] = useState(false); + + useEffect(() => { + document.querySelector("html")?.setAttribute("data-theme", theme); + // To resolve this issue, you can use the import.meta.env object instead of process.env. The import.meta.env object is provided by Vite.js and allows you to access environment variables in your code. + document.title = import.meta.env.VITE_APP_NAME; + }, [theme, openDrawer]); + + // Function on click drawer hamburger button + const handleDrawerStatus = () => { + setOpenDrawer(!openDrawer); + }; + // Function on click theme on themes list + const handleThemeChange = (theme: string) => { + setTheme(theme); + console.log("Theme changed to " + theme, "info", 10); + }; + + return ( +
    + + {/* Root drawer container */} + {/* Drawer opening is controlled directly via css prop and next via local useState variable */} +
    + {/* A hidden checkbox to toggle the visibility of the drawer */} + + {/* The actual drawer content */} +
    + {/* Navbar */} +
    + {/* Left side navbar */} +
    + +

    {main.program_name}

    +
    + {/* Right side navbar */} +
    +
    + + +
    + +
    +
    + {/* App/active_drawer content */} +
    + {/*Automatically generated breadcrumbs based on routing table from configuration file and active path*/} + {/*Get active route and find it in routing file*/} + + +
    +
    + {/* Drawer sidebar wrapper */} +
    + {/* Dark overlay on mobile devices, clickable to close drawer */} + +
      + +
    +
    +
    +
    + ); +} + +export default App; diff --git a/src/features/Debugger.tsx b/src/features/Debugger.tsx new file mode 100644 index 0000000..3388869 --- /dev/null +++ b/src/features/Debugger.tsx @@ -0,0 +1,31 @@ +import { useLocation } from "react-router-dom"; +import { flatRoutes } from "../configure"; + +function Debugger() { + const windowLocation = window.location.pathname; + const locationHook = useLocation(); + return ( +
    +
    + + Flat Routes + + {JSON.stringify(flatRoutes)} +
    +
    + Locations +
    + Location hook: {JSON.stringify(locationHook)} +
    + Location window: {JSON.stringify(windowLocation)} +
    +
    +
    + Routes + {JSON.stringify(flatRoutes)} +
    +
    + ); +} + +export default Debugger; diff --git a/src/features/Home/HomePage.tsx b/src/features/Home/HomePage.tsx new file mode 100644 index 0000000..2d4d6bf --- /dev/null +++ b/src/features/Home/HomePage.tsx @@ -0,0 +1,5 @@ +function HomePage() { + return <>HOME; +} + +export default HomePage; diff --git a/src/features/Login/LoginPage.tsx b/src/features/Login/LoginPage.tsx new file mode 100644 index 0000000..770d52b --- /dev/null +++ b/src/features/Login/LoginPage.tsx @@ -0,0 +1,102 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState } from "react"; +import { SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { z } from "zod"; + +type Input = { + username: string; + password: string; +}; +const InputSchema = z.object({ + username: z.string().min(4, "Username must be at least 4 characters"), + password: z + .string() + .regex(/[A-Za-z\d@$!%*#?&]{4,}/, "Minimum four characters") + .regex(/(?=.*[A-Z])/, "At least one big letter") + .regex(/(?=.*\d)/, "At least one number") + .regex(/(?=.*[@$!%*#?&])/, "At least one special character"), +}); + +function LoginPage() { + const [isSubmitting, setIsSubmitting] = useState(false); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(InputSchema), + }); + const navigate = useNavigate(); + const onSubmit: SubmitHandler = (data) => { + if (data.username === "admin" && data.password === "A0m!n") { + console.log("Login successful!", 3); + navigate("/products"); + } else { + console.log("Wrong username or password"); + } + setIsSubmitting(false); + }; + const onError: SubmitErrorHandler = () => { + console.log("Errors in form fields"); + }; + + return ( +
    +
    +
    +
    + + {errors.username && ( + + )} +
    +
    + + {errors.password && ( + + )} +
    + +
    +
    +
    + ); +} + +export default LoginPage; diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/src/main.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..702fa7b --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { RouterProvider } from "react-router-dom"; +import { setupAxiosInterceptors } from "./api/AxiosService"; +import "./main.css"; +import router from "./routes"; +import ConfirmationDialogProvider from "./components/ConfirmationDialog/ConfirmationDialogProvider"; + +setupAxiosInterceptors(); + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + {/* fallbackElement - use when routing table is quite big to load, it will display desired element in convention of loading screen */} + + Loading route table...

    } + /> +
    +
    , +); diff --git a/src/routes.tsx b/src/routes.tsx new file mode 100644 index 0000000..642a91a --- /dev/null +++ b/src/routes.tsx @@ -0,0 +1,69 @@ +/** + * This file is used to configure react-router-dom. + * Do not change anything here, unless you know what you are doing. + * Every path and route is configured in `configure.tsx` file. + */ + +import { createBrowserRouter, RouteObject } from "react-router-dom"; +import { CustomRouteObject, navigation } from "./configure.tsx"; +import Redirect from "./utils/Redirect.tsx"; + +/** + * This function is used to convert `CustomRouteObject` to `RouteObject` and process all additionalProps + */ +const createRoutingFromCustomRouteObject = ( + routes: CustomRouteObject[], +): RouteObject[] => { + const navigationRoutes: RouteObject[] = []; //Root routes that will be used for navigation + routes.forEach((route) => { + // Extract everything except additionalProps and children + const { additionalProps, children, ...routeNative } = route; + //Filter routes that are disabled for navigation + const isRouteDisabled = additionalProps.disableRedirect; + if (isRouteDisabled) { + return; + } + //Add route to navigationRoutes, array that will be returned + const isRouteWithChildren = children !== undefined && children.length > 0; + if (!isRouteWithChildren) { + navigationRoutes.push({ + ...routeNative, + }); + } else { + navigationRoutes.push({ + path: route.path, + element: route.element, + children: createRoutingFromCustomRouteObject(children), + }); + } + }); + //In case of empty routes, add a default route with message + if (navigationRoutes.length === 0) { + navigationRoutes.push({ + path: "/", + element:
    Empty routes
    , + }); + } + //Always add redirect unmached route to root at the end + return navigationRoutes; +}; + +/** It is main variable that handle all navigation rules and paths */ +export const routes: RouteObject[] = [ + ...createRoutingFromCustomRouteObject(navigation), + { + path: "*", + element: , + }, +]; + +/** It is variable that have routes for react-router-dom, final form */ +export const router = createBrowserRouter(routes, { + basename: "/", + future: { + // Normalize `useNavigation()`/`useFetcher()` `formMethod` to uppercase + v7_normalizeFormMethod: true, + }, +}); + +export default router; diff --git a/src/utils/ObjectUtils.ts b/src/utils/ObjectUtils.ts new file mode 100644 index 0000000..47b3177 --- /dev/null +++ b/src/utils/ObjectUtils.ts @@ -0,0 +1,9 @@ +export const rollThroughObj = ( + obj: Readonly>, +) => { + let result = ""; + for (const [key, value] of Object.entries(obj)) { + result += ` -${key}: ${value}`; + } + return result.trim(); +}; diff --git a/src/utils/Redirect.tsx b/src/utils/Redirect.tsx new file mode 100644 index 0000000..64a35d7 --- /dev/null +++ b/src/utils/Redirect.tsx @@ -0,0 +1,18 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +/** + * @description Redirects to the given page + * @param {string} to - The page to redirect to + */ +function Redirect({ to }: { to: string }) { + // The navigate function from useNavigate is used to navigate to the given page + const navigate = useNavigate(); + // useEffect is used to navigate to the given page when the component mounts + useEffect(() => { + navigate(to); + }); + // A null element is returned because the component does not need to render anything + return null; +} +export default Redirect; diff --git a/src/utils/RoutingTableUtils.ts b/src/utils/RoutingTableUtils.ts new file mode 100644 index 0000000..c778233 --- /dev/null +++ b/src/utils/RoutingTableUtils.ts @@ -0,0 +1,79 @@ +import { + CustomRouteObject, + RouteObjectAdditionalProps, + flatRoutes, +} from "../configure"; +import { + clearMultiplePathSlashes, + trimPathOfParameters, +} from "./StringTransformationUtils"; + +interface FlatternRoutingTableElement extends RouteObjectAdditionalProps { + path: string; + name: string; +} +//! WARNING: This function will generate error if paths aren't unique, disableInNavbar or disableRedirect to prevent this. It's a useful feature to prevent duplicate path in navbar +/** + * @description Convert a existing routing table as a flat array + */ +export const flatternRoutingTable = ( + routes: CustomRouteObject[], + previousPath = "undefined", +): FlatternRoutingTableElement[] => { + const result: FlatternRoutingTableElement[] = []; + routes.forEach((route: CustomRouteObject) => { + if (route.additionalProps.disableRedirect) return; // Skip if disable redirect, children are skipped too + if ( + typeof route.path !== "undefined" && + typeof previousPath === "undefined" && + !route.additionalProps.disableInNavbar + ) { + result.push({ + path: trimPathOfParameters(route.path), + name: route.additionalProps.name, + disableBreadcrumbBar: route.additionalProps.disableBreadcrumbBar, + disableInNavbar: route.additionalProps.disableInNavbar, + disableRedirect: route.additionalProps.disableRedirect, + }); + } + if ( + typeof route.path !== "undefined" && + typeof previousPath !== "undefined" && + !route.additionalProps.disableInNavbar + ) { + result.push({ + path: trimPathOfParameters( + clearMultiplePathSlashes(`/${previousPath}/${route.path}`), + ), + name: route.additionalProps.name, + disableBreadcrumbBar: route.additionalProps.disableBreadcrumbBar, + disableInNavbar: route.additionalProps.disableInNavbar, + disableRedirect: route.additionalProps.disableRedirect, + }); + } + if (route.children && typeof previousPath === "undefined") { + result.push(...flatternRoutingTable(route.children)); + } + if (route.children && typeof previousPath !== "undefined") { + result.push(...flatternRoutingTable(route.children, route.path)); + } + // Errors handling + if (typeof route.path === "undefined") + console.error(`Route ${route.additionalProps.name} is missing path`); + }); + return result; +}; +/** + * @description Function to find element in flattern routes array by LAST path ex: /admin/MAINTENANCE + */ +export const findElementInFlatRoutes = ( + path: string, +): FlatternRoutingTableElement | undefined => { + // Split path string into array, split by '/', then get the last element + const _route = flatRoutes.find((route) => { + const pathArray = route.path.split("/"); + if (pathArray[pathArray.length - 1] === path) return true; + else return false; + }); + return _route; +}; diff --git a/src/utils/StringTransformationUtils.ts b/src/utils/StringTransformationUtils.ts new file mode 100644 index 0000000..b96728d --- /dev/null +++ b/src/utils/StringTransformationUtils.ts @@ -0,0 +1,22 @@ +export function capitalizeFirstLetter(string: string): string { + return string.charAt(0).toUpperCase() + string.slice(1); +} +/** + * @description Function to clear multiple path slashes + * @example clearMultiplePathSlashes("/admin//MAINTENANCE") => "/admin/MAINTENANCE" + */ +export const clearMultiplePathSlashes = (path: string): string => { + return path.replace(/\/{2,}/g, "/"); +}; +/** + * Trims parameters and queries from a URL path that start with ':' or '?'. + * + * @param {string} path - The URL path to trim. + * @returns {string} The URL path without parameters and queries. + * @example + * // returns "/path" + * trimPathOfParameters("/path/:param1/:param2:param3/?param4") + */ +export const trimPathOfParameters = (path: string) => { + return path.replace(/\/:[^/]*|\?[^/]*/g, ""); +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..a22a179 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +import { themes } from "./daisyui.config.js"; +export default { + content: ["./src/**/*.{js,ts,jsx,tsx}"], + // safelist is used to allow classes to not be purged by tailwind + safelist: ["alert-info", "alert-success", "alert-warning", "alert-error"], + daisyui: { + themes: themes, + }, + plugins: [require("daisyui"), require("@tailwindcss/typography")], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f468454 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,49 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noErrorTruncation": true, + /* "react-jsx" and "react-jsxdev": These are new options available from TypeScript 4.1 onwards. "react-jsx" transforms JSX into calls to a function that will be imported from react/jsx-runtime. "react-jsxdev" does the same, but for development builds. These options are useful if you're using React 17 or later, which introduced a new JSX Transform. + Remember that to use these options, you also need to set the module compiler option to esnext or commonjs, because the output will contain import statements.*/ + "jsx": "react-jsx", + /* Vite handle emiting */ + "noEmit": true, + "useDefineForClassFields": true, + /* + "strict": true in TypeScript's tsconfig.json is an overarching setting that turns on a number of strict type-checking options. Some of these could overlap with ESLint rules related to good practices in JavaScript and TypeScript coding. However, since ESLint and TypeScript serve different purposes (ESLint for style and syntax, TypeScript for type checking), there usually isn't a direct conflict. + "noUnusedLocals": true and "noUnusedParameters": true in TypeScript could overlap with ESLint's no-unused-vars rule, which flags declared variables or arguments that are not used anywhere in the code. + "noFallthroughCasesInSwitch": true could overlap with ESLint's no-fallthrough rule, which disallows fallthrough behavior in switch statements, a common error in JavaScript. */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": [ + "@types/node" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ], + // "references": [ + // { + // "path": "./tsconfig.node.json" + // } + // ] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f5de01d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, loadEnv } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default ({ mode }) => { + process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; + return defineConfig({ + plugins: [react()], + // resolve: { + // alias: { + // "tailwind.config.js": path.resolve(__dirname, "tailwind.config.js"), + // }, + // }, + }); +};