From 7391940b13b00a4157d5d8e771a954ea02068b15 Mon Sep 17 00:00:00 2001 From: Justin Ferrari <‘justinwesleyferrari@gmail.com’> Date: Fri, 8 Dec 2023 13:56:56 -0500 Subject: [PATCH] Initial q-blog commit in its own repo --- .gitignore | 24 + .prettierrc.json | 10 + index.html | 14 + package-lock.json | 6695 +++++++++++++++++ package.json | 54 + public/favicon.ico | Bin 0 -> 15406 bytes src/App.tsx | 67 + src/assets/img/arrr.png | Bin 0 -> 1429 bytes src/assets/img/btc.png | Bin 0 -> 1982 bytes src/assets/img/dgb.png | Bin 0 -> 4917 bytes src/assets/img/doge.png | Bin 0 -> 1798 bytes src/assets/img/ltc.png | Bin 0 -> 1480 bytes src/assets/img/qBlogLogo.png | Bin 0 -> 2506 bytes src/assets/img/qort.png | Bin 0 -> 1907 bytes src/assets/img/rvn.png | Bin 0 -> 2405 bytes src/assets/react.svg | 1 + src/assets/svgs/AccountCircleSVG.tsx | 25 + src/assets/svgs/AlignCenterSVG.tsx | 21 + src/assets/svgs/AlignLeftSVG.tsx | 17 + src/assets/svgs/AlignRightSVG.tsx | 17 + src/assets/svgs/BoldSVG.tsx | 17 + src/assets/svgs/CodeBlockSVG.tsx | 17 + src/assets/svgs/H2SVG.tsx | 17 + src/assets/svgs/H3SVG.tsx | 17 + src/assets/svgs/ItalicSVG.tsx | 17 + src/assets/svgs/LinkSVG.tsx | 17 + src/assets/svgs/NewWindowSVG.tsx | 25 + src/assets/svgs/UnderlineSVG.tsx | 17 + src/assets/svgs/accountCircle.svg | 1 + src/assets/svgs/interfaces.ts | 5 + src/components/AudioElement.tsx | 230 + src/components/DynamicHeightItem.tsx | 96 + src/components/DynamicHeightItemMinimal.tsx | 39 + src/components/FileElement.tsx | 445 ++ src/components/common/AudioPanel.tsx | 253 + src/components/common/AudioPlayer.tsx | 192 + src/components/common/AudioPublishModal.tsx | 366 + .../BlockedNamesModal-styles.ts | 28 + .../BlockedNamesModal/BlockedNamesModal.tsx | 100 + src/components/common/Comments/Comment.tsx | 336 + .../common/Comments/CommentEditor.tsx | 258 + .../common/Comments/CommentSection.tsx | 386 + .../ContextMenu/ContextMenuResource.tsx | 82 + src/components/common/CustomIcon.tsx | 16 + src/components/common/DownloadTaskManager.tsx | 289 + .../common/DraggableResizableGrid.tsx | 55 + src/components/common/ErrorBoundary.tsx | 36 + src/components/common/FilePanel.tsx | 257 + src/components/common/GenericPublishModal.tsx | 317 + src/components/common/ImageUploader.tsx | 89 + src/components/common/LazyLoad.tsx | 47 + .../common/Notification/Notification.tsx | 86 + src/components/common/PageLoader.tsx | 43 + src/components/common/Portal.tsx | 25 + src/components/common/PostPublishModal.tsx | 281 + src/components/common/PublishAudio.tsx | 111 + src/components/common/PublishGeneric.tsx | 120 + src/components/common/PublishVideo.tsx | 112 + src/components/common/ResponsiveImage.tsx | 124 + src/components/common/Tipping/Tipping.tsx | 289 + .../common/UserNavbar/UserNavbar-styles.ts | 55 + .../common/UserNavbar/UserNavbar.tsx | 135 + src/components/common/VideoContent.tsx | 51 + src/components/common/VideoPanel.tsx | 284 + src/components/common/VideoPlayer.tsx | 832 ++ src/components/common/VideoPublishModal.tsx | 287 + src/components/editor/BlogEditor.css | 78 + src/components/editor/BlogEditor.tsx | 574 ++ src/components/editor/ReadOnlySlate.tsx | 25 + src/components/editor/customTypes.ts | 47 + src/components/layout/Navbar/Navbar-styles.ts | 112 + src/components/layout/Navbar/Navbar.tsx | 490 ++ src/components/modals/ConsentModal.tsx | 70 + src/components/modals/EditBlogModal.tsx | 247 + src/components/modals/PublishBlogModal.tsx | 281 + src/components/modals/ReusableModal.tsx | 47 + src/constants/mail.ts | 3 + src/global.d.ts | 61 + src/hooks/useFetchMail.tsx | 469 ++ src/hooks/useFetchPosts.tsx | 362 + src/index.css | 162 + src/index.d.ts | 9 + src/interfaces/interfaces.ts | 8 + src/main.tsx | 19 + .../BlogIndividualPost/BlogIndividualPost.tsx | 951 +++ .../BlogIndividualProfile.tsx | 301 + src/pages/BlogList/BlogList.tsx | 225 + src/pages/BlogList/PostPreview-styles.ts | 134 + src/pages/BlogList/PostPreview.tsx | 320 + .../CreateEditProfile/CreatEditProfile.tsx | 7 + src/pages/CreatePost/CreatePost-styles.ts | 14 + src/pages/CreatePost/CreatePost.tsx | 194 + src/pages/CreatePost/CreatePostBuilder.tsx | 1409 ++++ src/pages/CreatePost/CreatePostMinimal.tsx | 1390 ++++ .../components/Navbar/NavbarBuilder.tsx | 261 + .../components/Toolbar/EditorToolbar.tsx | 157 + src/pages/EditPost/EditPost.tsx | 562 ++ src/pages/Home/Home.tsx | 7 + src/pages/Mail/AliasMail.tsx | 279 + src/pages/Mail/Mail.tsx | 342 + src/pages/Mail/MailTable.tsx | 190 + src/pages/Mail/MailThread.tsx | 315 + src/pages/Mail/NewMessage.tsx | 425 ++ src/pages/Mail/ShowMessage.tsx | 256 + src/state/features/authSlice.ts | 27 + src/state/features/blogSlice.ts | 331 + src/state/features/globalSlice.ts | 137 + src/state/features/mailSlice.ts | 345 + src/state/features/notificationsSlice.ts | 73 + src/state/store.ts | 31 + src/styles/fonts/Cairo.ttf | Bin 0 -> 353464 bytes src/styles/fonts/Cambon-Light.ttf | Bin 0 -> 123472 bytes src/styles/fonts/Catamaran.ttf | Bin 0 -> 182788 bytes src/styles/fonts/Oxygen.ttf | Bin 0 -> 46440 bytes src/styles/fonts/Raleway.ttf | Bin 0 -> 309720 bytes src/styles/theme.ts | 179 + src/utils/blogIdformats.ts | 29 + src/utils/checkAndUpdatePost.tsx | 23 + src/utils/checkStructure.ts | 49 + src/utils/extractTextFromSlate.ts | 14 + src/utils/fetchMail.ts | 73 + src/utils/fetchPosts.ts | 53 + src/utils/time.ts | 24 + src/utils/toBase64.ts | 174 + src/vite-env.d.ts | 1 + src/webworkers/decodeBase64.js | 62 + src/webworkers/getBlogWorker.js | 74 + src/wrappers/DownloadWrapper.tsx | 240 + src/wrappers/GlobalWrapper.tsx | 667 ++ tsconfig.json | 19 + vite.config.ts | 8 + 131 files changed, 27283 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.ico create mode 100644 src/App.tsx create mode 100644 src/assets/img/arrr.png create mode 100644 src/assets/img/btc.png create mode 100644 src/assets/img/dgb.png create mode 100644 src/assets/img/doge.png create mode 100644 src/assets/img/ltc.png create mode 100644 src/assets/img/qBlogLogo.png create mode 100644 src/assets/img/qort.png create mode 100644 src/assets/img/rvn.png create mode 100644 src/assets/react.svg create mode 100644 src/assets/svgs/AccountCircleSVG.tsx create mode 100644 src/assets/svgs/AlignCenterSVG.tsx create mode 100644 src/assets/svgs/AlignLeftSVG.tsx create mode 100644 src/assets/svgs/AlignRightSVG.tsx create mode 100644 src/assets/svgs/BoldSVG.tsx create mode 100644 src/assets/svgs/CodeBlockSVG.tsx create mode 100644 src/assets/svgs/H2SVG.tsx create mode 100644 src/assets/svgs/H3SVG.tsx create mode 100644 src/assets/svgs/ItalicSVG.tsx create mode 100644 src/assets/svgs/LinkSVG.tsx create mode 100644 src/assets/svgs/NewWindowSVG.tsx create mode 100644 src/assets/svgs/UnderlineSVG.tsx create mode 100644 src/assets/svgs/accountCircle.svg create mode 100644 src/assets/svgs/interfaces.ts create mode 100644 src/components/AudioElement.tsx create mode 100644 src/components/DynamicHeightItem.tsx create mode 100644 src/components/DynamicHeightItemMinimal.tsx create mode 100644 src/components/FileElement.tsx create mode 100644 src/components/common/AudioPanel.tsx create mode 100644 src/components/common/AudioPlayer.tsx create mode 100644 src/components/common/AudioPublishModal.tsx create mode 100644 src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts create mode 100644 src/components/common/BlockedNamesModal/BlockedNamesModal.tsx create mode 100644 src/components/common/Comments/Comment.tsx create mode 100644 src/components/common/Comments/CommentEditor.tsx create mode 100644 src/components/common/Comments/CommentSection.tsx create mode 100644 src/components/common/ContextMenu/ContextMenuResource.tsx create mode 100644 src/components/common/CustomIcon.tsx create mode 100644 src/components/common/DownloadTaskManager.tsx create mode 100644 src/components/common/DraggableResizableGrid.tsx create mode 100644 src/components/common/ErrorBoundary.tsx create mode 100644 src/components/common/FilePanel.tsx create mode 100644 src/components/common/GenericPublishModal.tsx create mode 100644 src/components/common/ImageUploader.tsx create mode 100644 src/components/common/LazyLoad.tsx create mode 100644 src/components/common/Notification/Notification.tsx create mode 100644 src/components/common/PageLoader.tsx create mode 100644 src/components/common/Portal.tsx create mode 100644 src/components/common/PostPublishModal.tsx create mode 100644 src/components/common/PublishAudio.tsx create mode 100644 src/components/common/PublishGeneric.tsx create mode 100644 src/components/common/PublishVideo.tsx create mode 100644 src/components/common/ResponsiveImage.tsx create mode 100644 src/components/common/Tipping/Tipping.tsx create mode 100644 src/components/common/UserNavbar/UserNavbar-styles.ts create mode 100644 src/components/common/UserNavbar/UserNavbar.tsx create mode 100644 src/components/common/VideoContent.tsx create mode 100644 src/components/common/VideoPanel.tsx create mode 100644 src/components/common/VideoPlayer.tsx create mode 100644 src/components/common/VideoPublishModal.tsx create mode 100644 src/components/editor/BlogEditor.css create mode 100644 src/components/editor/BlogEditor.tsx create mode 100644 src/components/editor/ReadOnlySlate.tsx create mode 100644 src/components/editor/customTypes.ts create mode 100644 src/components/layout/Navbar/Navbar-styles.ts create mode 100644 src/components/layout/Navbar/Navbar.tsx create mode 100644 src/components/modals/ConsentModal.tsx create mode 100644 src/components/modals/EditBlogModal.tsx create mode 100644 src/components/modals/PublishBlogModal.tsx create mode 100644 src/components/modals/ReusableModal.tsx create mode 100644 src/constants/mail.ts create mode 100644 src/global.d.ts create mode 100644 src/hooks/useFetchMail.tsx create mode 100644 src/hooks/useFetchPosts.tsx create mode 100644 src/index.css create mode 100644 src/index.d.ts create mode 100644 src/interfaces/interfaces.ts create mode 100644 src/main.tsx create mode 100644 src/pages/BlogIndividualPost/BlogIndividualPost.tsx create mode 100644 src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx create mode 100644 src/pages/BlogList/BlogList.tsx create mode 100644 src/pages/BlogList/PostPreview-styles.ts create mode 100644 src/pages/BlogList/PostPreview.tsx create mode 100644 src/pages/CreateEditProfile/CreatEditProfile.tsx create mode 100644 src/pages/CreatePost/CreatePost-styles.ts create mode 100644 src/pages/CreatePost/CreatePost.tsx create mode 100644 src/pages/CreatePost/CreatePostBuilder.tsx create mode 100644 src/pages/CreatePost/CreatePostMinimal.tsx create mode 100644 src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx create mode 100644 src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx create mode 100644 src/pages/EditPost/EditPost.tsx create mode 100644 src/pages/Home/Home.tsx create mode 100644 src/pages/Mail/AliasMail.tsx create mode 100644 src/pages/Mail/Mail.tsx create mode 100644 src/pages/Mail/MailTable.tsx create mode 100644 src/pages/Mail/MailThread.tsx create mode 100644 src/pages/Mail/NewMessage.tsx create mode 100644 src/pages/Mail/ShowMessage.tsx create mode 100644 src/state/features/authSlice.ts create mode 100644 src/state/features/blogSlice.ts create mode 100644 src/state/features/globalSlice.ts create mode 100644 src/state/features/mailSlice.ts create mode 100644 src/state/features/notificationsSlice.ts create mode 100644 src/state/store.ts create mode 100644 src/styles/fonts/Cairo.ttf create mode 100644 src/styles/fonts/Cambon-Light.ttf create mode 100644 src/styles/fonts/Catamaran.ttf create mode 100644 src/styles/fonts/Oxygen.ttf create mode 100644 src/styles/fonts/Raleway.ttf create mode 100644 src/styles/theme.ts create mode 100644 src/utils/blogIdformats.ts create mode 100644 src/utils/checkAndUpdatePost.tsx create mode 100644 src/utils/checkStructure.ts create mode 100644 src/utils/extractTextFromSlate.ts create mode 100644 src/utils/fetchMail.ts create mode 100644 src/utils/fetchPosts.ts create mode 100644 src/utils/time.ts create mode 100644 src/utils/toBase64.ts create mode 100644 src/vite-env.d.ts create mode 100644 src/webworkers/decodeBase64.js create mode 100644 src/webworkers/getBlogWorker.js create mode 100644 src/wrappers/DownloadWrapper.tsx create mode 100644 src/wrappers/GlobalWrapper.tsx create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfe8e32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +*.zip +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0a4d4fc --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "printWidth": 80, + "singleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "avoid", + "tabWidth": 2, + "semi": true +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..55edf06 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + +
+ + + + +;_c!90Z@9Q?-nTYx`eWo3(#l&6HnF$1xmg7EIlsl|7;B;++4=SBZy!$)Ua&<&91 zKpkTe&SoKC(>D?S<{J5f`gyUNSlbArqhrX&A_w`3hkx_mgwTFrV+(n=yY%l9f8;t4 zk1a+F+-&RO^{9CrmQu&Y+W~9`3ic5Gdy#*@`>2^I$fZh}rHQwr#`Z_b1#+4td-||H0N!K2^?*-smJNuBP#ad7x >Q2 z4DkvlIqq=gnTdyoQz2~t|15GcbC_?Y4Pbt)(0OyHaWhw)O^Byq5B~z5p8#j%X_%*1 zK6mMkdKc(@Df=-7F9hFU-vItrMisBL<_N{iXY3CCoS$ hlJ6E(k6(zx0c8M;C<6UO`dYz8~#{#$SgqEeExSi c#bL6`aen$kZe9GzJ7M^*o+a$VIstMU z2X5CQ2c;lW%=s{n?#IFZ+=BZj;2GRWL2b*3+omr6#jYNGu)mM@OkCQGVX~#g&PN_{ z7WzI&*jz2$Quk=dB(CrE_1ga8kNpgF>oKqerY2WG*Qn63b9slutd30JcW+LA%Nhx7 z33vzhBb3dy7Q4zx3f*cY>`y$H2eRjjms*AlJu27Xyqe&5Zi4UjdR=*JdYE6 (cFzk@!I@!815 zX1ux&&TMCI{zJd !@DGY+Vgo4a_FoLK!#TXH#bLK z7vi6jH*hv*(1rm2_C0|={E3@@Lp{#53xBuvi~3zsx{7ia#D5m#5&Uy9^v(*L+gf*@ z9e0fUeJO9`d&DY>F#onL`V1WXfyYhs4Iqoqh429Yj#1{?JjlciVg4=8w-t3Gj9W_l z&4hR^>mh#J`OJ4#Ql5-{i*dd<)BOAc$v 6JQ z`j?^pBE1gUUDg1b8^nwEa;)0u&mrT8t(6w&+ZintBVxPsqnyC5peA-3{80LmI{>af ziTHO_bCq~~PxR@szdhS8;s#rCBY0pV&X;rVbX)k^(4Vun&M!GC%#OjH?hSwLOyJJ= z+Ci=9?qIFn+Y9U2tev55IvaZEWt Vh-_x{ut@u;crPZ=%$OX-;Y7pcIeTnU>)AypSl$`eSA7u`V-bonQNdwnR{kT zF>&?x<#QMQaoD?_=}$i3Jx==3=^E*H*4LSfVtp`-F5Ws>V?>Pc8)#4ai!)PJI;uRi zw=r022>l(_!`zw}WIGJ^Lhxr_)^Vw)Nh7XLn}d0K^y9!gLu@sA3~)}myaWFk=a6D; z%1V^{sJA_M`|qozeZiTY0e!onH~RNAPw+eW9`&p;#6zgPE9=0=`53^B$q;91@&jot z`h(?H^z8cu=M4MJdl$GX@;l7WSk `$qHa_ef=}ui@CdC<;lX|}$Eoe`h#5e}_ z4{-qQ|NK3pi|6r 2u1V(TeVS#;*Rop(0?_E-)-1OyEpvFJB&Nh z(BHu|aNnmWmmCqzB*%V#s}VaBI^@rJ+#$1Wtlnu)8AEap&|yjw&YXFhhv}n27feql zZ0Ijw@63 t;0I1&$ +pH!}7;r#s0CdM)c-(u`l=p4iv0@HkPv9RW>tFUA?Go+|oP;x9fjIlW z&EaK&vXXf)>Tc+h{c_N)*vC-rk-jnSheE!(`Tt#hdf)u>WoaEY zo_3k|BmcJoGO!kQWd{D2 { + // setIsDarkMode("dark"); + // } + + return ( + + + ) +} + +export default App diff --git a/src/assets/img/arrr.png b/src/assets/img/arrr.png new file mode 100644 index 0000000000000000000000000000000000000000..d27456576e737c1353e256810d9b84dce7b42784 GIT binary patch literal 1429 zcmV;G1#0?+ ++ + ++ ++ + + +} + /> + } + /> + } /> + } /> + } /> + } + /> + } + /> + } /> + } /> + k}ogVt_iv;g(_nl!MG8$U!DoWdu9kkw6h-ftOt@TB<<3sK5#b z%0a@oI37WePGIvKtR6|AI9mhAv#hf;g
!DQH`61I`vLh+0A4)5ZAgMw_(fIw3) zJ;zl&sHJKOrdX)Hx;#!mMwh{?4+1iNCMv%cs&_F=hEbm`q|2jkHpr9){rIV#T^^%{ zqGI` FG|T-NsX|E 0@RAuy6F+A}zL1- lD6p(ZBB$8#sMK3e0-2 zyN`|tb@-8)tA&n=Zp7vthfK?qt?H*&?8Ec>6y1K*Zl$YZC?)`2i2k;rbN5(Ja(f&> z+I84U^Cc*)6S}%Am?43*v&+{5%?Ss(|8(3zHn20hJXOrjyFuJRHpKXrg=S^dV)^SF z2l1dSSLXN6@BBXR1AV4vV;|eVi@+ZM7|I3uWBCi4oxV;L$+&{tePqNO-{FT843s4> zt0X1g`RVPW&|um@5()C9DX`ECi?KogX;&s8bNr+h1Qa2GTYhodULx1%dy;)-VmsA8APs0umM;p4i-Xy#(i%@Nrq=){EG``n z8)S$XAXhDYofCb_5&*4s8%WbSm%jwg^81isSt#ifaMl(afEg7*a+^RVP@R{Xlpw?2 z%hXKPIu?O+%R)&sbZ#P2AVGhAeK+?v5vio%PTVq$g+>dewJ1n1c7n>l((*&0+sF37 z1hC4FuZp}(QZ5SWcE=w6xh%tCDC7HY^JVk?3&u g07?j k#AM% zp8|1O^(-h(zSY1=b8vN(m9}V|ZWap4hn1x0U^z0 D!f$4jecbWOsfvh5d+Q34B;Czhy%(n6vZ9lYt6&UXDakYjKP`=$833& u30U=V6#}s8^*@Ag)eEW!;kNq< jH3JJkl>Q2}zXA*Z%L07o@pf>Q00000NkvXXu0mjfhXs}` literal 0 HcmV?d00001 diff --git a/src/assets/img/btc.png b/src/assets/img/btc.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3fd1a030bd2736ed76748d27e6584b3fa05c3a GIT binary patch literal 1982 zcmV;v2SNCWP) htPv-zHc{0_qe;RVIF+Z!7aDH+~!cJ9zWE z0z3R;&-88FK?e2QLmp!@E8cvh&hc=%{kbT8{6vWNHYd)RK99@$W6Yf$C}t1aw#Jzg z0V;Sa!^E?jk>}QSm5SwX6l)KBQTk+0IO|oeTiQv=s9ZvBZmmTrZ`ziI&lxG$;Z &v6ZUQj_sWG~M))W^e7D$CuHznMHr>Q@{nSfDQWq}b2MbIgTT zz^IxC6f5m)AyTYIo(7ztPO6RN45fh8bmc!vp{zk(9*5z6*bt5knS!?+5r9(7+s`Og z?bu8S)%ld|@)Abn>wnn39_;7E^Qti19)(pp?UZ&|FD+GddH97GzI-gMPL6m?%H8Hf z;BTPqMk*8w0Dl_&ZDq&7%d6~1XOj+Y*lnpJ?8F$`y-}IZHKxuO#X?@LytmFKZ7cjp zm4`i*Ihj;x7-&P-YUihk;4^t|1PduCvl(bi0LA>&TAed0Bbo?>P7amrmG>zweyU3w zysJEYn(tq0utMD!<@h6odbVIvDn(&+f_WHr_a)eS%A;Q$({d2!R!ywkXPrCd6#^(F zE{%&VAxye_@kptbl5T=h8B2ZFK(TUCE2=^P6o8w1j#TaCuT9$ZYHYq2>ZO~Y(7LeL zlI$ln^MPu<3X>9tK5KXD?jqUG#B*OK+el}f^+}HBMpaA}!G0FB)vW^6VL#I<9!vvc zvui_j+0SthPjEQFzdcrkmTd*}6g5Giqr6@n7oAlwe?FK!4{E5NwMc {tc%(KO0l2AS2uN1C~N>&=eDPwE0M1Q&!51npP5^&=$w5ojIT|E z`sCapF9DqY_ 0 z+J| C!i9BebTjc0EMs#dhkT|H *HdVFr3qr0yEoCT-l&?ra>O#pQXH}u1CnGm{ZR#upDJd zAPX%9211iOc{)tQg^rKrimI7*G0K8b0H%eSJuz4R{c!3Df9z-(?jPmPNWh7eU c%AtW tvpeHm= z)d4#RUVIebD|nSnlKm`D8_XNQu|T16f-CC|w8w@32%HSnX%!3T+ 1sU?sB+R+~g}N)wvhE_5HY3Wvi3>K5JK!&P>kH!4c@j{x84Y3h8GeDB zP=mU;AbI8p7680=3~K8A?@~?0dB>pgZzebeb(w8yKMPE So@ zz}%N0bFE#}vP(EFZElOj-3oJn8Bp#5s+Reocq3Jgf{KYk2i&`^_lB%q3{cb;14Mv@ zoOe&GUR}WgcJ*=<2C%Ew|1iQ`y`YK_Zuk{yRXozXLhTek0iyL+sQoX%0DJn9yuv^) QjsO4v07*qoM6N<$g5oNn-~a#s literal 0 HcmV?d00001 diff --git a/src/assets/img/dgb.png b/src/assets/img/dgb.png new file mode 100644 index 0000000000000000000000000000000000000000..6950158b56d73dcce67b8fece5cc1d1817b6b51c GIT binary patch literal 4917 zcmV-56Uyv~P) Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D63R(LK~#8N-JJ<^ z6ji#%|F=2|Nk||G2qLS1vM5X-K?u$OA|Q+BJ>{_}+h`KdVcv*}^3HP@^|+wpGR)}s zNa7Zb7(o#oKtUFX$d*JvS!Kt7VGXin?Yis&61ocU4z+x)Yq=IhDTmcBi`PU*G-i zcW>RgEl|&Blk R-t0UPo1Vol$3k ziu8Q4HZfKTc^|#@frO^s3KcqS8|Vz@LppPjbsJlC$O*ZiB7Ji=3GoPo7z-(*>fkHA zAWP|cbIC2tL1wRQsxTyk?(EVEl4 p)?5uz W4xM(2D)JNZvQ_24g%c1LPXL#w{U|>VS$>JmmCBt? z*O6)(goE601Z|OxfT+fh4X*=elVJ^9y9x-APf@`{nVqs*6$DJ}1 A0o+VUhG1Lq0d^hyi7OKPw znNLSQ&~Ej&b*2=ohf@__LMcn1$vYE8`Ox>RrsKFatkMj&R;~R2=&i^;RG9@U$cRQD zyQj~F%2sS4h2wQ!VnRBh?OF!8_bUpCu5B?HNyqElN5{I0?1a)j7)&Qc8u-Syg=XYW zB1PX*Ij;z31;qA6(9cjj`71 Xf!Hu1y<|v?)Y0xg5 zl@Q-AfPVIfD*1JyHEsjhZImxjFH(mj*{VlnTIEIB`Y>o=IaXqFRT0}iAu)qGzECgl zIq *^mPg~&lVX=8%94;7J*lCw6AHQBVaJ E|4YS~S#Q=`@#dso&;gxR!lEW*RCTzl&7qWJVVqjipnibPzD8W^Dhj_A&w zzlgH(zy^<~hXm1KkkR^E!2SL%)IIg^bKV6_FZhuDj&)@`5$BHqh4bj>bvq$Pt!NY* zg`#y?Xc!%-N?eRce-X>R-h$lM|AbM)Qz;ChQ{jW5*Y`eIjo;7wr|W|OU~~X_+z-9c z4X%B9Lm(HK$>UV+GM-gFy$Xj&^0dz>UyO3fk*HOlI`3R1%XdkgvUKD=n+QhVOPG zv2jD(J%}vH7+IqCSXAQ6wOfqWK;e)IJC{&{YhmS*G)m8Bt=#Ysln#k+TbTG(>YE?R zBZH?NP#y96kporg3ZJgngpzZYFlIzwL`6m#GFMZ4vIL7)ZC1I!(M!)jP}kW4(nzpY zZqz&$?h$n-$@ucRl~BjJx{vKzO)m!>FoijaNlnOcY*t zd^|K|c`hX0{&+PCw;#l`@k7w1Ln~G8B6rC;%$S{nW5>_b+P@oBKV>rXnCn!D2jsHl zZ>U@Py~ Dh- pns($kQq_Ntsco|!%l5n&d7vmOZ3H2n9Z5%?@~ zCZZzji>ja5Z)jivY3ITv=#)Yp80=%DyTlGklX>l5f2c_YUc;a@QV%Z%@K2|1*hF zHqVM3C~06p9||{(!Bv-_QXh6XeHA m&*ta+sW~;#sQRb- z(O}B*3lL|y%{D_C8VWJ#Yat^VQeP5Yrxc0 0P#NqaoE|@lP zC{mL;sdw!0{hmYUKH@P-**jjXb#LhH2dJC}#iFddg+Rupc94z(s?Y<$f&7KzDtGPT zAaS32Y%FF@8L3KqbFs7>aowoTuJXRnVw*!IPv!<(mAE6M^F|F)OHz3cgtH8|a8l*2 zZMgbB^=dA@r9Qr0A|t}!aM*9{J#KpHXFQXlH+d{@n*Gy($SF zJ#T4;7atpsN~aT*RaMX{>6OwIP90Sgh&!t!em+%#*>e|>CGkvfd$3>3qz;80E|rxd z_E!5A$}UDE@hFsgcee(!sLJQ(khBw2fg4&;A@sc~R4%x@JNscw8hM8g? @^yUpw`>&Si?5jx8HFBYhu3pHXJ{hKQK@p*nomBCQ+9(D$0Gp z*c(6mcmx}_au3b^Nw;s+%(ndN^8Jgi4LF1}Kb+ 5D_mBVs+3m}SWtcr$P09xo#riCpbYHY6*sl>Jq4z$ zyb>QQwjAK~Al#v0=m1my=EkP2KjL6f04}YHA|G>NS MfFWp}O>ItutLs@uv}_iSy9f4CIcwftXuoBz z=8uzy)5Y?Axqd!bg51y7QOd5jt% sahL>^rZ 7c9|JST~Ls5KI!ho)lfhtHvT6MN4f&3G{fU*y@lP?k{RXFR!Q zirAr1^lmy4h#&V8v9((pV9c;o#5XqSjMYGwI^O;GYnAg(v&1;edwCji|2`eDF?`3{ z_r-qhjRko9y(M%RYFpXq+t|Rkl{bS|mC(vA9ipUp!xV2i_GgPJ_YE?4;Wb#j{s$cR z$@`rF7V_CYjlrH3|G@Zr29S&_q_vpbSAXiY4=`)aN2;WDHH{JRZRxU@?x&cS=+N|> z0d(njSqbmRCicP+Z=`f>hve=zshl-$FK`Wn@Zs_;srId|!viDlz~Z-N;BektJT+|; z;^ORuaiwMDnE2QnyztgyD*^mQHtDERUdiM2PF&xXUXVtAvyAlB(3RFUyaB!3zkW57 z_54(%*$n1at|QMw4Of%+MrcQ3#x;x%NQylNj$+&+uVTwCo(o%jiE59CjD_s;FeS|D zn@C4i&urt{MB3&SQfNO>((9+S18blk-l%f+NQjNbk#(7fG3IuL9;Z{sKXR5(HUAKo z%BzejA565?w?S`zhst@tqb#kEm3+()P-gey+sGR@?Un1hlj{Cm>GAjULv$U9nYVcd zdf)p5roWJbi)EET7t=WR>$|CpS2nPioTzD$^VO>ddQ^hz;ZvLN&8#WoxMvjP6LUji z ZchEAJ?32r6^wkNI$Pn^gK^&r18>))k#Qw4_vU z{8vrNDk|{Nl6B}e_RmNi^}krUc1uVvn?XA|cXO*Rr3%m58C(%GD}}ldhNxcQ=gsX} z=|kfN2T@9i@`@^~{B|22qON1B)C`QB{x^J6xI6Sh5?W#hI(Ie>Ge|QvH 0+7pW?D39jDvaqhX`X1X5g~wWD&1qF2dUUUHGP87tUYg+cDMq zs|zXV+o(ksy&i-f-9(jmz$<$EMrJ2po)Yffs%Q41H+Wqm@5q=Y#`ES@GTqfb2_@%T z&k5<&hvu5Xuin`4;}K(Y>G-L$s^kjm@^@h7D<5Ln+O0}VhJ~yfF&WV+g?y)}n9S0e zVltlExE>YRL~pr^tq^)8S?N2FymE~N4%%}#Pj%58n0#+Pyzz8~ddJ0C61`<=8X_XX zDZHAChSBbmrzbu>2lGDrM&;@rK@G7?dBAF+0=AM~)VdeCvMxMD%Sz#gWqFY(uShK% zqRZX%5$jtSPBJ96Ql5~-gCjNj(wttn^jlo2sKogH>}QBs)9y;nUBD;H{fm2p3P1hP zb%gb*4+0CW7L)N_d(#VeHVKbHnLe86DB29?UcQlL o;61^hMh zbNqPtXFQqlAL!Vg+HyW#zhU$R)w+3rEL+&|CGgXxRDDMA?Om&*r0vOnUwk^!7_C z=K=qt*tHCLaY)zZt8)Z(Vq_;{GO`8oNfPsAm2tzXpEQu-HQIf?%6Y&SPwzy*d%}&b zf`0=vK9`$)&T7ers3dK?V=icRauqLMMk#*s);sJ+Z@C9r 7J3Cao&GF$rM<%kBs&j- z=y)f!=)Ml_8i0<`(cw-w>6mTIkXa5<#Txj;wke6U{8%Zlic;1~w9y{5bH{+4|AOu# z_^&TSRJ;*O>v22_(5D3Xnv?_e>>w_Lb(yrnL 3|>qyY$&pmLbsS;HLmI}7E;pIRAlZXiE~GRL+hcRCSij;qHYFRq?Sl6c$+?u zjavG1J$W|SC~Aa4zue_z(!Qfkz0S**1+*Y5KRX%7jXyi^oGj6-vvKQ2k4f}LNajt5 z^6ALzTZap>)+Bv11^*f^L1VsPmKXRzRO8vnQ@mvGA@KUkyf9247`~AwA;h=Tp+3W6tSHR%QX5IT=i@!C%|I}UY@PG`P=e9TO0 zc&^PK2Jj#~zbEbfX-W;JEp*@GA7uBxvY)Q-AS1sUq qA@+DE~$UtHYraavO!zwGbd5C>`I2KenQO<*~d?)@$&(4ADzZ_ nGRQ5CveHA&dG+}dYQX;k6d8Qv8i;)j00000NkvXXu0mjfe}R$- literal 0 HcmV?d00001 diff --git a/src/assets/img/doge.png b/src/assets/img/doge.png new file mode 100644 index 0000000000000000000000000000000000000000..d99fa2ff2a6b1c969c23a22d9d265fa2c872a9ac GIT binary patch literal 1798 zcmV+h2l@DkP)
m>denkSs zAba6f4~k2CXk5I&G6xDlBEQ&Qgdo#_?Sf#Xq6CVgH3E4OC`*PgSSHzlhGEq9Di9{X zWA@@}C_GR&5TuoZd5qjV1dV+VUrS+39-FvS1ksyEHsH$D1!!`F>BQBEXfOf3CJ(+w z{u+u;Bq`j)X^R3^wgrJkg(>S-rEE1-qrzkhmCEFC0P-jxS?5^ ^y*5u)N{`w^RzVB5G)_FlEmER%=`PrI@wu?G;X^=#>R3f_A0^2~DZ)x!2C> z8yp`;@}Z^48rmaju2cJbrxAh;aro+ %F zZ`@d4{vYhkAG^WE+7i_<4bD&27!SiG4A8Bv#ljeb`XLomZP{KUV=a9G6nB+`iSE(x z6qp$Klxp9q#*;zD(cXkK)CmL=q^WY5LSl)kEvsaCYNtjNb5EAJ$*-wQpK%k#C8UPN z1C@VrfViK@cErc kTf+&% zQ>A(f$v^HidMElvn)+W6Kxb~8O9@Km>Q{X_0#KS@Lo0hIfYP@7XiYvMvI!c@P5C@s z1OfGk477p(G`4h?Wi9VPK)u4kG&K=@=*DW8WnlIS4b;}yPTc2#f7rkps3c_iQffdM z1#!R7{56)fyd 0=v5Y%p zVa)#N7rQ~@VY-`izT4+*EX_T!^i(`h)ZjqRgw`MGrrJLmnGP^EcWJ9qLbDo12HH@9 zuc&4-3Oq(zYfg{jOpl&rshyGd%#g&=6aAvISj;vf#4-b{ewrc7N@CKUN&cB03Pu*L zvwXJca=Kc+uULT6nr$w!x)KP^dit!4zqT*2mIv7EJ)%YXbZOF`KcBX;Y=+?I?nwcf zOj<;kH?DT-7zd&hX4`<`wTyhC!CZ~bheofw#{;G>dHpA$XMw~6(AW>}@8dKXEQM93 znRKXO!eurHubFfyO^ uMWFZ-L?gE^qCi0ZE*!{r5?DysKcP<$r zNE_$Vb*1~I+-zi&TQOhQiq+&1|2yZCj9+5J-Mgy_cW;hN+Bi?AdDfwAD>0I zCWX7$us|t9(7Gu)5)9fWN NJJB(GWl>$RvUsB5EazVAH+?iXc;TbQ8TK57#9N ztZ<+dB>2U&h9I55CMj4`OQ0C70pvLGmn AbZlDzAG(9{b zjUjUsYElVMG;vdd=q)1~i1WGtE})oGF8Uzw)Dy50Jh`iDpa|3SvA=ZVSL1G>x&j&? znA}jAfd&vJ7gQ#o7=Sh!*k`i%)F?}!G8jM53@}WBzn!U~G8nWo-GHePj|^eX&2REp z0WE|qOXPO7imD<|^qaW=1|gOp)l eni0gA!GC5VIpJs~k*lG7XMFP!i zJKN&=yv)}fWvLXhs;QwV6<0UhAK^N0T^&$IR)!ARS=3Osvitso9m3nC-1Cr1#P}znc4L^@I#hH8ig3O2%S6eoU8~B(4Qep6Cwc=GM1e zJ%M6ZNm;*hn4{dxY-*>S#c1CN8X8q~1(EaZx-__L(sPmzHm%F&y zb(PN1BriSjp-K{ZUjJFi4eb2d*CN3;l$kqc|I@h){h>{mZwR0|m7&clz7!&Ky#n2) zl}w(z`|w4ps>w5;kS~JYoVYQ_mCP4qbv;ksuWgZ)^}LpueX|#)eLoSa+r%L)dh+U{ z>PH^xedq+OVnp~$&`8~3dIFRudG}fUUIw%v)7*KIcc%cPVY|*r3IJ5vqS9eOsS!kT zpU4IP-IJ-IR^gq8 @c0>pWmD<&S#lQ#?NzE9k10fn^5q~n?BPi|{H zb281)s$FBTE3Q8WZipszI*XXivNRMR*g^Pgb{(GhJKS+26$5uJe((XMtt0QUbG= z`k; w4 z+z5*;2#MVYE5K0YZPgY)sPZ=xv_TEGyA%Z!p@ti>iUeu_o2G|*4FYq9f~;%bfN)c5 zOF5~ zj2~H$H?da0000 K~#7F?OJ_o z6vq{RGrRX;`_6YZHV(0Ukn&MJB?u-;swO2;(=-qo2>1*nX+%hqG%8iqf2#D4QvONR zHmxc_Tct{YAPV-G64X?65=cou6qFK*ihwqy7;|Ss0e{_xeIL6!uW!%xdCy+kw}%hh zh@Wn6cV^zqynQq8&6_vNpum`YI67Tb#Ti7Xgh>Q4yx5o0>bjqv7J>g$$Z$)*ID&{X z&X1cGBR)X?)4&oK8+uw8Kzl#-t{O_KB6hWXQ|(7E#c_*-uY+|2>r2;|mG}bTb6{Ec z!-zn b&ZQ$S#j!;&q*t00yuR8$LSBsKj$F4k6`pI~0RvbZhi56LdDtibx&SA(( z%qfvxgIz $T01ABE8U zGw?HQWSe&fBA=C&=ss%VKA_Qp^bf~of(RMIC!FATke;8;o^2eS5BW=f2YISpM}zw~ z_5E*{Y|x7bH+gALwY4OhBv=I^sa50%z4}^8*+-XyLc&%}YEEZ}Kd!~_*G3k6CV(E9 z!C`9qKClSdp*-r{+v_n1pPg}Gs1a@wD6}3Z5Q6@)4{2Wh8Z2T~bJ$O}FJ)ySHVf2N zyCCTfHhAozdXG($ODj+lTWEk=4U~-V>g>Zf-B6d*Ap8TmFU(|-^`&bU3bB@kvQ7iB zOuuM%e=h>xNNvW3tAn?s)ZsFi9o&81zc^*e{GJ{!mcYHVQJtZuHK*-^_BF*FX0(h; zHn51=L%nsfhQ<1`kkuHARGe?y!V;q!XQ&+v@8c4@3>H8_c_}+Bc7+ M 2ODDg|of)_RC4;LK7`~8OAy3`cBHeQ|Uo2#x-?C4urJOZxQXT1I_NzS?SJZ-oP zE=9Sv55~plH6ty2l6*`B5j3mflpRk{#X4gi#$!$>95{IeONEGj!>mxNt%{&Qg(rg2 ze>a&1H1Lb3uRi4sPE@bz=!Ii6pEKG=gR!`#xxFm?45HddON;Eu9)EclEMjQpCyElx z)K=4|E*+h#LBS!!e6#Gfm5@T+E6cf=RIM>h-c6@&z9y=7>Q{9N3Z=`CRn9bLdJxI3 zXGLf!YIqUyKw9`r2sNy9#LWnBOux0jKv86xPx{XQClp?$8O}?MX_4rQ=UNxyQDwMW z=CDJFlzx`7*ELN^TAuYf24+5&kh#mvUx-EqZUe}}Fz+|eOcO2lm=sysEH#yiUAfnF zd50YmvNZ{NJS`)kDp_rkk6~$Flc_h*u#l3Gq}_}Fk+;Kb0Quymi|VYt6*k&M+95}v zre>K_6zI;es%~;!nR*N&YM(Ob4f1Rg&7lnP03Ynd>Git3<`mj{x~f9~L4qE&!I3zB z&x}?rk&9`^R17(ST;B^>X&S$UfwL11-kd OMi8nm{|mWln@RfbQ1Zj{rQttdGJ$Fn{a+?qbCnWjXnKCXZG(HSIdSP6viLdA z-cW<5Rd+8l8RSXDz&GKpG>Pgf0LCzhIC=HO>VEx6lkB>;lrf96e1EPc&{+)@F=YS9 z;g`QK7E!3)-5BzF9t*;|59wF?bF*H7Nkpij{8ic&F4H7fqB#2=2sV`M7|SCeMpuIs zq}#!_jZKg(-8 R8=;-;&)0U3N$vON?PHRuaEw 5L% zl8!G1iwJITpYt7xY$G54m-PP9KrVCAJ3l)RfW<*0VxnveuA>Gowr#m5E8IZ~@S7BL zdyXc8fgiSnT3cf331 dt>qto zY^!dJ%&Tn-liK48kSBuc-EU~9ts^1m>sAhtQS2fSwg`&*hc sokft@6tdG zI5iC7y|OJa*2$1Q zu3rtr%k@N1}gbSEU4b08Wcg^io=Gvo7KW%$G2U7T8$HTf? z`_bTa^PtV(Wq`lKz-5Yi_V>)xY +G|@e9*ru4YWKQtv_8U2J-a;34s+6mrI4ptQy%|WmHssSran*qABz0E UFWAIlcK`qY07*qoM6N<$f{LrPWB>pF literal 0 HcmV?d00001 diff --git a/src/assets/img/qort.png b/src/assets/img/qort.png new file mode 100644 index 0000000000000000000000000000000000000000..39d090f724b9c2708a771b0208dd41266d433cdf GIT binary patch literal 1907 zcmV-(2aNcMP) |H@`8$}eJETAGPXj)W25R?Ukkb0?o>#feEH&j>ZjYC{+oG`zj+g}jp#Eota zy;VvgZoPDK>n+ZKNC>sqA|RlG+EhhU2*P~ZnP9uS_Uz1?nT@qiS~*_N&c6Nf=Dj!H zyxm OGX0pw+NykuJdOOhP~gi*V948deV=bpF*|A2Bu zI{ahPVdS|i#QPO|PN@24C7cI;P^qL!*`p{~IUUweslF22PsfAH#fj&uJpYw!Hb zdVl;~N=sD&R0N=|i)4NA#0#ulKf|tHI6Jl7je8GSXZLqDc>0g4NkB~{P^k|2#08kG zbBpQFo&_cZZF--6m>%OmmIVgp7AkVHRoi|t;s9M-dPVE_or6DF#4R>Ccw!sab#A3? zZ9v8BVbi9c1dyvlR<^M}NP)HNU$0E#S#tpuezRvQ2m#1;{dIQgL_pf_@aezYxBiv_ zYcWWLdH!_T)x I(cT(yZ>ozo@%j$)f&(Db`7r2E~`&X1hFky9N`2bK>040lE-MtYo2rX}?KvM&y&x zg*U7WZC(Ew-TO7oUy0<}B=esIXwNj~L>dH(Smsf@jMC>>K |)}u7X02Om~Zw&l8 zPh$Y2PCC;O$F9?*LRtncS52jbHhgG-DEl4<1HQ|I*Tk{t1AU=SPtx!PDE#CV?gtyU zdrBz_BCtvnMqlU?efQll`eT4Du(fZ%Dm~Keg-185Oc1h&ND$6Au1FNtVYK+rH39WB z05u{)iU4~0%Wsv3b{^?mluU+YKl qK~pz=)9NI zI8E?_3IkfH0IdM60IdM6qyn@8be;hnRxpss5P)`lYXM5nguleG=%WPE1)u}pT5R2a ze6&nlWPJ#2 kn*;M&Ul*!a@J;+=i!an?l zHjcSY6-k_0Xc7Jr<%d`wy2gQnkHm(*vCl$jBAE|OD@PVO``UNK6 iwJAbb2gHJOJdF8__-FE>AamnB!jv=CRt4U}+rKR4V*6Z^!62C^8|uvjZS zwgim*|A30S8oVX=pV!Z%{uKqfMWuaotXIZ h&?#zHxYVN)?F>Q%MQJPU=isLm>jVe`li7eZ0gtBO&O6BUv5Z #4b_PsMTa6fs#(uR5iKFD_blZy;kcA}tFY Ooj)L5TK2SiV3_hsM%L4(yxH$dR32Ts%rh8_dFh`U2Hs zdSv%uf+F&FUHMdx($@h~%R)yiwR#{cC7>g~f*@F(6EJ5!ip@Hp0#w}E93Nx9IutV? z<8?I>)>bGIQWGkU1UiRQ?+vM53{ch=17rXTVkKhLt1GMkTfJPx0 txTCL73&mFC6>0 )ZFgHh+XWI*A(+)*H2Pr02S_y9H;q2nn5Y xCa+3PzDjTu9 %eSn>fgNpp|wG zHjtJ;Imi~axgfP9g2!bQSkr+rkmwf!DF{*yY!d?;Oi7?TS|iA(A-$xHgEb{PaF`gi z!)XW;_%Vn1cc2H#4y2Xc9I!ERTOoMt2iSKBkeNI;@n8x>Z!59^f1)nHkswSTuTmBo zOyJ)s#J{8U8p{7sFwIR|Z&BbkS`auEVXFGopixcLt)Zy^^Qp&u@YtgdLT08Dsw+z& zcX<|!j!nX=uLq!`vo9qyRToemfI<|>dT#DA*tN3(c0Jo507N U%?ShL)x!Y_fBEf~FGpdbT z*5HlV4}MOBi?=4oGirTShmP9Zc0m(I4V4PQT~l5Nz<&RAwX4Bf zT?UcJAY8vb0w+#(!lrt+?7r}G=n9@>0Zr*W1|`7R$cj_#gH#b615jD%y?>3S?Q}Z8 zwYC_tvYfD<0YzEv$`X9&z`!l$4kD13mmT9CEHe87&Zbef$@3Q65vug)aN8cie0#6l z01wl| Jvg$gFZz{ovzdy<*^%_vBG+}Sk%cc-SI# +ywvomv{Nq0N-qdFSpF z+!>pKr7WUXtSN%hmH7$n3sWi!%TSPE7MQ1@y!k=a->hO4&cx(hxHU2X;qE~={#PfQ zJ=Y`nNuy%3+1E=uQqEM+PBc;f_VwWPl*vZ&GiM{PWQhYV_uha!mhewc&p;t7GfRr| zWAat0w%hHk*wz#ew2{o*nQxq7`S8V#KBissUHC?xKGkignD-i%mQyn`(Dm;?w4LXw zD7O16G{z+Mlr=lbyKgo^rMrar%|g}0-H2d aBXlT;e8I@x*fgW*FS7_`%cyD5vZFIsI*L!M)+hPOioV4bffInP&Xw^sYzm_ zj|LT1yJJ%%p#s}=FAv2AvyDRQL$T(L7mW0xV4|OhWJr7{g@zWSg1Jj0y(s17_ZhC` z@%`f99#&DKqZ4+1r2+>Kid@Ydjr2^g+k_>h5s7r))9~NzHFrTsfl7yo<0HK&`T5z# zG*qIIXY4h1TA)fDq4-+fy|y?eQP&cj_eA7BX@M&BETxfNE>YK#+v9US7iW)92pLeh z2rn1u#S|+3IVCA7$|KWGQlM(%KGH}ZeQso2Um}>FGY2&EKLU!)3b{_ZT%;ETCxPME zyP9B`iTlM{8^@I90Ub^y?Ual3Vw0oy>P^+pjqnIhW$wrlfrjk 09C4@k2Kfl~lt*)W6kzPllpOc+Mt)XEO4HXC7R4X2ZsIHdi M!VWPt`# z$$zRvdNI*IezH^cH5^VM)J~M3?Hant=hpQ;xkxWc_OeXb=W+O$7--1B&unN d?F|bX1p|rJVs~54M|2X<)p@29!>N!GJveyl(LfoZB3O3FUshM`tSuN`r@?li rf(X1x%x}II^=dHQ%#g-D>#k^Ft)#M{>;O zou{;Q$6oa8rq8p-nQ-sCp?{4RY{7l>$wBHwU9&=F+8N`= EJ74sv;%rRMb$e zr9)~TG*oiRbm%eg?0l{@-CzoerndHoYUa%A(|u+}cp>nxyzwojnKn=4GDE)6S~XYi zsyGR+B8Ef8KYjOFx(1&3ib!+nr&H(7{E*->L!x$K;Av$kQJP8wbWWYOd?74c?g9tR zh EVGi4pzgw0!n&Z&6N3}{6O4hHdO3vbl|a@b={D~3j?I; zg#l#1Vq>KR7B5|43E0Jpt5^Vb@$x?w!d<*Tm4$Gnmr!%_&B!IxhWIT)B)^2(e*z2u Xq@DHT-WO?J00000NkvXXu0mjfNJg50 literal 0 HcmV?d00001 diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/AccountCircleSVG.tsx b/src/assets/svgs/AccountCircleSVG.tsx new file mode 100644 index 0000000..deb88bd --- /dev/null +++ b/src/assets/svgs/AccountCircleSVG.tsx @@ -0,0 +1,25 @@ +interface AccountCircleSVGProps { + color: string + height: string + width: string +} + +export const AccountCircleSVG: React.FC = ({ + color, + height, + width +}) => { + return ( + + ) +} diff --git a/src/assets/svgs/AlignCenterSVG.tsx b/src/assets/svgs/AlignCenterSVG.tsx new file mode 100644 index 0000000..b78991b --- /dev/null +++ b/src/assets/svgs/AlignCenterSVG.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from './interfaces' + +export const AlignCenterSVG: React.FC = ({ + color, + height, + width +}) => { + return ( + + ) +} diff --git a/src/assets/svgs/AlignLeftSVG.tsx b/src/assets/svgs/AlignLeftSVG.tsx new file mode 100644 index 0000000..4ac2eb4 --- /dev/null +++ b/src/assets/svgs/AlignLeftSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const AlignLeftSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/AlignRightSVG.tsx b/src/assets/svgs/AlignRightSVG.tsx new file mode 100644 index 0000000..2a2eea8 --- /dev/null +++ b/src/assets/svgs/AlignRightSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const AlignRightSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/BoldSVG.tsx b/src/assets/svgs/BoldSVG.tsx new file mode 100644 index 0000000..8d06816 --- /dev/null +++ b/src/assets/svgs/BoldSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const BoldSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/CodeBlockSVG.tsx b/src/assets/svgs/CodeBlockSVG.tsx new file mode 100644 index 0000000..1fad02f --- /dev/null +++ b/src/assets/svgs/CodeBlockSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const CodeBlockSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/H2SVG.tsx b/src/assets/svgs/H2SVG.tsx new file mode 100644 index 0000000..0328fc3 --- /dev/null +++ b/src/assets/svgs/H2SVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const H2SVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/H3SVG.tsx b/src/assets/svgs/H3SVG.tsx new file mode 100644 index 0000000..7268032 --- /dev/null +++ b/src/assets/svgs/H3SVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const H3SVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/ItalicSVG.tsx b/src/assets/svgs/ItalicSVG.tsx new file mode 100644 index 0000000..50a5493 --- /dev/null +++ b/src/assets/svgs/ItalicSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const ItalicSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/LinkSVG.tsx b/src/assets/svgs/LinkSVG.tsx new file mode 100644 index 0000000..9994f43 --- /dev/null +++ b/src/assets/svgs/LinkSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const LinkSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/NewWindowSVG.tsx b/src/assets/svgs/NewWindowSVG.tsx new file mode 100644 index 0000000..c97d483 --- /dev/null +++ b/src/assets/svgs/NewWindowSVG.tsx @@ -0,0 +1,25 @@ +interface NewWindowSVGProps { + color: string + height: string + width: string +} + +export const NewWindowSVG: React.FC = ({ + color, + height, + width +}) => { + return ( + + ) +} diff --git a/src/assets/svgs/UnderlineSVG.tsx b/src/assets/svgs/UnderlineSVG.tsx new file mode 100644 index 0000000..a65f597 --- /dev/null +++ b/src/assets/svgs/UnderlineSVG.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from './interfaces' + +export const UnderlineSVG: React.FC = ({ color, height, width }) => { + return ( + + ) +} diff --git a/src/assets/svgs/accountCircle.svg b/src/assets/svgs/accountCircle.svg new file mode 100644 index 0000000..2edae3e --- /dev/null +++ b/src/assets/svgs/accountCircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/interfaces.ts b/src/assets/svgs/interfaces.ts new file mode 100644 index 0000000..6a0d5e1 --- /dev/null +++ b/src/assets/svgs/interfaces.ts @@ -0,0 +1,5 @@ +export interface SVGProps { + color: string + height: string + width: string +} diff --git a/src/components/AudioElement.tsx b/src/components/AudioElement.tsx new file mode 100644 index 0000000..5b807ee --- /dev/null +++ b/src/components/AudioElement.tsx @@ -0,0 +1,230 @@ +import * as React from 'react' +import { styled, useTheme } from '@mui/material/styles' +import Box from '@mui/material/Box' +import Typography from '@mui/material/Typography' + +import AudiotrackIcon from '@mui/icons-material/Audiotrack' +import { MyContext } from '../wrappers/DownloadWrapper' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../state/store' +import { CircularProgress } from '@mui/material' +import { + setCurrAudio, + setShowingAudioPlayer +} from '../state/features/globalSlice' + +const Widget = styled('div')(({ theme }) => ({ + padding: 16, + borderRadius: 16, + maxWidth: '100%', + position: 'relative', + zIndex: 1, + // backgroundColor: + // theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.4)', + backdropFilter: 'blur(40px)', + background: 'skyblue', + transition: '0.2s all', + '&:hover': { + opacity: 0.75 + } +})) + +const CoverImage = styled('div')({ + width: 100, + height: 100, + objectFit: 'cover', + overflow: 'hidden', + flexShrink: 0, + borderRadius: 8, + backgroundColor: 'rgba(0,0,0,0.08)', + '& > img': { + width: '100%' + } +}) + +const TinyText = styled(Typography)({ + fontSize: '0.75rem', + opacity: 0.38, + fontWeight: 500, + letterSpacing: 0.2 +}) + +interface IAudioElement { + onClick: () => void + title: string + description: string + author: string + audioInfo?: any + postId?: string + user?: string +} + +export default function AudioElement({ + onClick, + title, + description, + author, + audioInfo, + postId, + user +}: IAudioElement) { + const { downloadVideo } = React.useContext(MyContext) + const [isLoading, setIsLoading] = React.useState (false) + const { downloads } = useSelector((state: RootState) => state.global) + const reDownload = React.useRef (false) + + const dispatch = useDispatch() + const download = React.useMemo(() => { + if (!downloads || !audioInfo?.identifier) return {} + const findDownload = downloads[audioInfo?.identifier] + + if (!findDownload) return {} + return findDownload + }, [downloads, audioInfo]) + + const resourceStatus = React.useMemo(() => { + return download?.status || {} + }, [download]) + const handlePlay = () => { + if (!postId) return + const { name, service, identifier } = audioInfo + + if (download && resourceStatus?.status === 'READY') { + dispatch(setShowingAudioPlayer(true)) + dispatch(setCurrAudio(identifier)) + return + } + setIsLoading(true) + downloadVideo({ + name, + service, + identifier, + blogPost: { + postId, + user, + audioTitle: title, + audioDescription: description, + audioAuthor: author + } + }) + dispatch(setCurrAudio(identifier)) + dispatch(setShowingAudioPlayer(true)) + } + + React.useEffect(() => { + if (resourceStatus?.status === 'READY') { + setIsLoading(false) + } + }, [resourceStatus]) + + React.useEffect(() => { + if ( + resourceStatus?.status === 'DOWNLOADED' && + reDownload?.current === false + ) { + handlePlay() + reDownload.current = true + } + }, [handlePlay, resourceStatus]) + return ( + + + ) +} diff --git a/src/components/DynamicHeightItem.tsx b/src/components/DynamicHeightItem.tsx new file mode 100644 index 0000000..a6ea00d --- /dev/null +++ b/src/components/DynamicHeightItem.tsx @@ -0,0 +1,96 @@ +import React, { useRef, useState, useEffect } from 'react' +import ReactResizeDetector from 'react-resize-detector' +import { Layouts, Layout } from 'react-grid-layout' + +interface DynamicHeightItemProps { + children: React.ReactNode + layouts: Layouts + setLayouts: (layouts: any) => void + i: string + breakpoint: keyof Layouts + rows?: number + count?: number + type?: string + padding?: number +} + +const DynamicHeightItem: React.FC+ ++ + {((resourceStatus.status && resourceStatus?.status !== 'READY') || + isLoading) && ( ++ ++ + ++ {author} + ++ {title} + ++ {description} + ++ + )} ++ {resourceStatus && ( + + {resourceStatus?.status === 'REFETCHING' ? ( + <> + <> + {( + (resourceStatus?.localChunkCount / + resourceStatus?.totalChunkCount) * + 100 + )?.toFixed(0)} + % + > + + <> Refetching in 25 seconds> + > + ) : resourceStatus?.status === 'DOWNLOADED' ? ( + <>Download Completed: building audio...> + ) : resourceStatus?.status !== 'READY' ? ( + <> + {( + (resourceStatus?.localChunkCount / + resourceStatus?.totalChunkCount) * + 100 + )?.toFixed(0)} + % + > + ) : ( + <>Download Completed: fetching audio...> + )} + + )} += ({ + children, + layouts, + setLayouts, + i, + breakpoint, + rows = 1, + count, + type, + padding +}) => { + const [height, setHeight] = useState (rows * 150) + const ref = useRef (null) + + useEffect(() => { + if (ref.current) { + setHeight(ref.current.clientHeight) + } + }, [ref.current]) + + const onResize = () => { + if (ref.current) { + setHeight(ref.current.clientHeight) + } + } + + const getBreakpoint = (screenWidth: number) => { + if (screenWidth >= 996) { + return 'md' + } else if (screenWidth >= 768) { + return 'sm' + } else { + return 'xs' + } + } + + useEffect(() => { + const widthWin = window.innerWidth + let newBreakpoint = breakpoint + // if (!newBreakpoint) { + // newBreakpoint = getBreakpoint(widthWin) + // } + + setLayouts((prev: any) => { + const newLayouts: any = { ...prev } + newLayouts[newBreakpoint] = newLayouts[newBreakpoint]?.map( + (item: Layout) => { + if (item.i === i) { + let constantNum = 25 + + return { + ...item, + h: Math.ceil(height / (rows * constantNum)) // Adjust this value based on your rowHeight and the number of rows the element spans + } + } + return item + } + ) + return newLayouts + }) + }, [height, breakpoint, count, setLayouts]) + + + + return ( + ++ ) +} + +export default DynamicHeightItem diff --git a/src/components/DynamicHeightItemMinimal.tsx b/src/components/DynamicHeightItemMinimal.tsx new file mode 100644 index 0000000..b80d53f --- /dev/null +++ b/src/components/DynamicHeightItemMinimal.tsx @@ -0,0 +1,39 @@ +import React, { useRef, useState, useEffect } from 'react' +import ReactResizeDetector from 'react-resize-detector' +import { Layouts, Layout } from 'react-grid-layout' + +interface DynamicHeightItemProps { + children: React.ReactNode + layouts: Layouts + setLayouts: (layouts: any) => void + i: string + breakpoint: keyof Layouts + rows?: number + count?: number + type?: string + padding?: number +} + +export const DynamicHeightItemMinimal: React.FC+ ++ {children} ++= ({ + children, + layouts, + setLayouts, + i, + breakpoint, + rows = 1, + count, + type, + padding +}) => { + return ( + ++ ) +} diff --git a/src/components/FileElement.tsx b/src/components/FileElement.tsx new file mode 100644 index 0000000..fee23cb --- /dev/null +++ b/src/components/FileElement.tsx @@ -0,0 +1,445 @@ +import * as React from 'react' +import { styled, useTheme } from '@mui/material/styles' +import Box from '@mui/material/Box' +import Typography from '@mui/material/Typography' +import AudiotrackIcon from '@mui/icons-material/Audiotrack' +import { MyContext } from '../wrappers/DownloadWrapper' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../state/store' +import { CircularProgress } from '@mui/material' +import AttachFileIcon from '@mui/icons-material/AttachFile' +import { + setCurrAudio, + setShowingAudioPlayer +} from '../state/features/globalSlice' +import { + base64ToUint8Array, + objectToUint8ArrayFromResponse +} from '../utils/toBase64' +import { setNotification } from '../state/features/notificationsSlice' + +const Widget = styled('div')(({ theme }) => ({ + padding: 8, + borderRadius: 10, + maxWidth: 350, + position: 'relative', + zIndex: 1, + // backgroundColor: + // theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.4)', + backdropFilter: 'blur(40px)', + background: 'skyblue', + transition: '0.2s all', + '&:hover': { + opacity: 0.75 + } +})) + +const CoverImage = styled('div')({ + width: 40, + height: 40, + objectFit: 'cover', + overflow: 'hidden', + flexShrink: 0, + borderRadius: 8, + backgroundColor: 'rgba(0,0,0,0.08)', + '& > img': { + width: '100%' + } +}) + +const TinyText = styled(Typography)({ + fontSize: '0.75rem', + opacity: 0.38, + fontWeight: 500, + letterSpacing: 0.2 +}) + +interface IAudioElement { + title: string + description?: string + author?: string + fileInfo?: any + postId?: string + user?: string + children?: React.ReactNode + mimeType?: string + disable?: boolean + mode?: string + otherUser?: string +} + +interface CustomWindow extends Window { + showSaveFilePicker: any // Replace 'any' with the appropriate type if you know it +} + +const customWindow = window as unknown as CustomWindow + +export default function FileElement({ + title, + description, + author, + fileInfo, + postId = '', + user, + children, + mimeType, + disable, + mode, + otherUser +}: IAudioElement) { + const { downloadVideo } = React.useContext(MyContext) + const [isLoading, setIsLoading] = React.useState+ {children} ++(false) + const [fileProperties, setFileProperties] = React.useState (null) + const [downloadLoader, setDownloadLoader] = React.useState (false) + + const [pdfSrc, setPdfSrc] = React.useState('') + const { downloads } = useSelector((state: RootState) => state.global) + const { user: username } = useSelector((state: RootState) => state.auth) + + const dispatch = useDispatch() + const download = React.useMemo(() => { + if (!downloads || !fileInfo?.identifier) return {} + const findDownload = downloads[fileInfo?.identifier] + + if (!findDownload) return {} + return findDownload + }, [downloads, fileInfo]) + + const resourceStatus = React.useMemo(() => { + return download?.status || {} + }, [download]) + const saveFileToDisk = async (blob: any, fileName: any) => { + try { + const fileHandle = await customWindow.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'File' + } + ] + }) + const writeFile = async (fileHandle: any, contents: any) => { + const writable = await fileHandle.createWritable() + await writable.write(contents) + await writable.close() + } + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) + } catch (error) { + console.log(error) + } + } + const handlePlay = async () => { + if (disable) return + if ( + resourceStatus?.status === 'READY' && + download?.url && + download?.blogPost?.filename + ) { + if (downloadLoader) return + dispatch( + setNotification({ + msg: 'Saving file... please wait', + alertType: 'info' + }) + ) + try { + const { name, service, identifier } = fileInfo + + setDownloadLoader(true) + const url = `/arbitrary/${service}/${name}/${identifier}` + fetch(url) + .then((response) => response.blob()) + .then(async (blob) => { + await qortalRequest({ + action: 'SAVE_FILE', + blob, + filename: download?.blogPost?.filename, + mimeType: download?.blogPost?.mimeType || '' + }) + // saveAs(blob, download?.blogPost?.filename) + }) + .catch((error) => { + console.error('Error fetching the video:', error) + // clearInterval(intervalId) + }) + .finally(() => { + setDownloadLoader(false) + }) + } catch (error: any) { + let notificationObj = null + if (typeof error === 'string') { + notificationObj = { + msg: error || 'Failed to send message', + alertType: 'error' + } + } else if (typeof error?.error === 'string') { + notificationObj = { + msg: error?.error || 'Failed to send message', + alertType: 'error' + } + } else { + notificationObj = { + msg: error?.message || 'Failed to send message', + alertType: 'error' + } + } + if (!notificationObj) return + dispatch(setNotification(notificationObj)) + } finally { + if (mode === 'mail') { + setDownloadLoader(false) + } + } + return + } + if (!postId) return + const { name, service, identifier } = fileInfo + let filename = fileProperties?.filename + let mimeType = fileProperties?.mimeType + if (!fileProperties) { + try { + dispatch( + setNotification({ + msg: 'Downloading file... please wait', + alertType: 'info' + }) + ) + let res = await qortalRequest({ + action: 'GET_QDN_RESOURCE_PROPERTIES', + name: name, + service: service, + identifier: identifier + }) + setFileProperties(res) + filename = res?.filename + mimeType = res?.mimeType + } catch (error: any) { + dispatch( + setNotification({ + msg: error?.message || 'Error with download. Please try again', + alertType: 'error' + }) + ) + } + } + if (!filename) return + + setIsLoading(true) + downloadVideo({ + name, + service, + identifier, + blogPost: { + postId, + user, + audioTitle: title, + audioDescription: description, + audioAuthor: author, + filename, + mimeType + } + }) + } + + React.useEffect(() => { + if ( + resourceStatus?.status === 'READY' && + download?.url && + download?.blogPost?.filename + ) { + setIsLoading(false) + } + }, [resourceStatus, download]) + + return ( + + {children && ( + + ) +} diff --git a/src/components/common/AudioPanel.tsx b/src/components/common/AudioPanel.tsx new file mode 100644 index 0000000..1d3d50b --- /dev/null +++ b/src/components/common/AudioPanel.tsx @@ -0,0 +1,253 @@ +import React, { useState, useEffect } from 'react' +import { styled, Box } from '@mui/system' +import { + Drawer, + List, + ListItem, + ListItemText, + Typography, + ButtonBase, + Button, + Tooltip +} from '@mui/material' +import VideoCallIcon from '@mui/icons-material/VideoCall' +import VideoModal from './VideoPublishModal' +import { useSelector } from 'react-redux' +import { RootState } from '../../state/store' +import { AudioModal } from './AudioPublishModal' +import AudioFileIcon from '@mui/icons-material/AudioFile' +interface VideoPanelProps { + onSelect: (video: Video) => void + height?: string + width?: string +} + +interface VideoApiResponse { + videos: Video[] +} + +const Panel = styled('div')` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + padding-bottom: 10px; + height: 100%; + overflow: hidden; + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-thumb { + background-color: #888; + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: #555; + } +` + +const PublishButton = styled(Button)` + /* position: absolute; + bottom: 20px; + left: 0; + right: 0; + margin: auto; */ + max-width: 80%; +` + +export const AudioPanel: React.FC+ {children}{' '} + {(resourceStatus.status && resourceStatus?.status !== 'READY') || + isLoading ? ( + + )} + {!children && ( ++ ) : resourceStatus?.status === 'READY' ? ( + <> + + Ready to save: click here + + {downloadLoader && ( ++ )} + > + ) : null} + + + )} ++ + {((resourceStatus.status && resourceStatus?.status !== 'READY') || + isLoading) && ( ++ ++ + ++ {author} + ++ {title} + ++ {description} + + {mimeType && ( ++ {mimeType} + + )} ++ + )} + {resourceStatus?.status === 'READY' && + download?.url && + download?.blogPost?.filename && ( ++ {resourceStatus && ( + + {resourceStatus?.status === 'REFETCHING' ? ( + <> + <> + {( + (resourceStatus?.localChunkCount / + resourceStatus?.totalChunkCount) * + 100 + )?.toFixed(0)} + % + > + + <> Refetching in 2 minutes> + > + ) : resourceStatus?.status === 'DOWNLOADED' ? ( + <>Download Completed: building file...> + ) : resourceStatus?.status !== 'READY' ? ( + <> + {( + (resourceStatus?.localChunkCount / + resourceStatus?.totalChunkCount) * + 100 + )?.toFixed(0)} + % + > + ) : ( + <>Download Completed: fetching file...> + )} + + )} ++ + )} ++ Ready to save: click here + + {downloadLoader && ( ++ )} + = ({ + onSelect, + height, + width +}) => { + const [isOpen, setIsOpen] = useState(false) + const [videos, setVideos] = useState