From 54868c027365da1fb9fd479f9fb1f36afe232939 Mon Sep 17 00:00:00 2001 From: Hugo Gresse Date: Sun, 5 Nov 2023 21:29:20 +0100 Subject: [PATCH] New team page (#59) * New team page * Add team to output jsons * Add save shortcut to all forms * Add save shortcut to all forms --- bun.lockb | Bin 171010 -> 171458 bytes firestore.rules | 4 + package.json | 1 + src/components/form/SaveShortcut.tsx | 16 +++ src/events/actions/getTeam.ts | 11 ++ .../generateStaticJson.ts | 4 + src/events/page/EventRouter.tsx | 28 ++++- src/events/page/EventScreenMenuItems.tsx | 14 ++- src/events/page/sessions/EventSessionForm.tsx | 2 + src/events/page/settings/EventSettings.tsx | 2 + src/events/page/speakers/EventSpeakerForm.tsx | 2 + .../page/sponsors/components/SponsorForm.tsx | 2 + src/events/page/team/EventMember.tsx | 55 +++++++++ src/events/page/team/EventTeam.tsx | 38 +++++++ src/events/page/team/NewMember.tsx | 41 +++++++ src/events/page/team/components/Member.tsx | 56 ++++++++++ .../page/team/components/MemberForm.tsx | 105 ++++++++++++++++++ src/services/converters.ts | 16 ++- src/services/firebase.ts | 3 +- src/services/hooks/firestoreMutationHooks.ts | 12 +- src/services/hooks/useTeam.ts | 8 ++ src/types.ts | 7 ++ 22 files changed, 418 insertions(+), 9 deletions(-) create mode 100644 src/components/form/SaveShortcut.tsx create mode 100644 src/events/actions/getTeam.ts create mode 100644 src/events/page/team/EventMember.tsx create mode 100644 src/events/page/team/EventTeam.tsx create mode 100644 src/events/page/team/NewMember.tsx create mode 100644 src/events/page/team/components/Member.tsx create mode 100644 src/events/page/team/components/MemberForm.tsx create mode 100644 src/services/hooks/useTeam.ts diff --git a/bun.lockb b/bun.lockb index aed7ccbc36af37fcd1ed661f83746b63f062cb1f..6f3d6298fa9bb96d4c9f3a685c720f4ff88359dc 100755 GIT binary patch delta 30382 zcmeHwd3Y2>_wH1a3F$z9KteKEfUvKD5JCch4Er{+DsNcZw1Q*tWi*I(S=-Dz{r6?Kq94>Vn1xOmN< zlU_CNow>qd?2j@oSAOoK@fJfZ6a2(+9-YE1`$vH^WSIh0eN#~#T7Mcwj8nw~_ys9Sz*@Eqz&d0J6`vc)mTm>FgxH7nxu#Pt>Fhj@17!%D zdFvXsd^32;>%h91arTJelcu1j>ejULX(C!Os+m(C@ zl)Yumq()ge*~o*#>F~FLr=2y|^!hTwQHP4MV?cwv@eS=(l>udI4m5DN5X|2W%I-PX z$S!FID7$ApD7$;8*3X%kJ0>f~;IajhZeLsEqNaz zSny6z*0@_6yC<4~vLgFHIrh!l+6im5m5T#wS8vnau169m>oGoSSSE^hMRc(1TdspV z9#|#M^4^Gc=85C9Z|1D8pFhsLaeS`hlpSB}_+ZEPI*WsL@_B!E#@Y!x&ew6ee$MuP z%hx(S((ycwN3uN+EbOMP2cE+6D4?{w4>9xE&zKS;#*7#?F`}2j%o- zZl~iL>iEdpWmIsLc5e@}+c5%d;3QZAS{}6Q2wQ#`JO?Q=J3H&nT$gJAcv^iAPL~yWej1yPzD6 zg`i=e&ww)iCFpT+oda4Cv=Z_Qhlx2g$zM0YUirUR=?S?|DyaI#kQZLK_)^Bo3ZY$0 zks&jpyy8_^fX_p+2%jG5iS~-VG9x-&%$09P*AeK@Xs_8kz~xFr{uSk+=wvZTX2f{S zFCfD*kslxz^-C7vvIw${ZkMYYWTh zbgPvP?Q}QCkae*%rXkcv4yxWtX4F7$z@~ose_a8qo(y2)ao%&6xz6=WRZ(pDFL3yx_q;Bm=jok*9f9XP`pq#T)1pTkp7 z-)p|*u{+;3jPInUf!Byx05z^Pd*tmT|R`-COAe};Dt5`D#$FL8Jx#nKTV^!zx;J6! zc99F>Q_WV@poP@1-TKMq6W~~71pm2b!A3#uQ{fU z%hke3YQ6%_wwFwd)SuFm>@}0%S_eVHB{7}E)3PAhE6&Iwd^VDv6mQ7=_3X(P;BvVz z>J1vm3UQ{)Nbwr=>dW~lY2sm7l;Rb`q^E_~tkuxvYK_dw$hd0B?uWs3m9dz6Um(O5 z2#$zpHgdTpTO3T1nEijx?cz1TE@0WHP0i|4Cw+m zWz2ewojec4C%cD(^HIx)Xd(x+N;CVy0%+etW{yq?0>d%2%+@RhcZ*$qRpgW4a`gaL zii)zJwb%V8BzMXM{Zd7y^rU&k0-2HKH4kHOPyuAPC_5zxQf>`ogD&Q47~^g1}U4w?;;%^$$E zcQlPUE#iW2(PG#wwsfXk2qd>zRqhO$2980mi)!4ufo$CiY2B7q2@B$}MDZN~*Y9 z7Jz>Rd@6Z&pqF;aP)~%qTcPI>y4}*gAd9+sjs9(9RJSy@M2B>i#Zjr^xGd=AHM+Ew z`?{r>kG8dIi_XOAut8?{yk-un%K2PM9*RqLzXHz3{LHfmb%eYe-Mjk^^c$RG%~W$C zLTnG5ZhW%wU3zLlf^@_podrNkwrbcMrtsfMulrZ<9poTiYTdTIZF6zMr}|$hkYj(8U;Es&)yHRhy300Ek>X|P z$?$qgqr+g5`PV18EGJ~tF*@~;^E1-Wy+s*bZ0kG&yk_aX_7t#ZRdQcBe?S`6x*|k< zj;J=sE0iwZEPJcHe3!QDu{}80X>_XlNrd2sv9H-KJp;W)z-@BCz%;Y@ZFWDyU8bkV zqJds_bu1~JWbwdM_b7zY7~;lrkk|b!_&!!le7d7E6QK^4Y&$}*7No1u9~H7fQxL+& zZD6YaOB7ot)hwT3Hw0cEQ@{rfUJ6qnvnH1-&k(QJBr}G1&2U(c9wBTl(!sT|d^mPx znO?Zkj7+b4$3PgA9F&>r4j;sc%+NrDx>})E5VB*Bm&g+DaP;m)$d3IOAv;CTVEPN@ zavMUn>;;5+TC!6mu`P!dfotC?3r2cPW4NthJM+82wX`(2t0|NjqrC3QBcP$? zbP|cOS5%PM2Q23k3_dE^d=?y*D>^5b|5k9etD^T0y3;9+oAoZ>+S-M~pUeaoPjfO0 z5n>Kl0eht|3B9%Du*~FzDY@wPQjfMYs1KrB#kqwKkiO2Ll%YR=aD z2^?2-%T=1qvg{JD5T+%Y+2ELqwWl+efU_-%jX4O8eFrykTT0MfC2K1dmHyxuWoJ1L z9J|o&kzL^0fwT7mVWUfi0~PB9t_kAkr&0pQ$U!lw<{E_9A$A9!17~Lt*E`uv9AnoP zYbVYs^T0LbqGN7l$SRaw{TH}SC0x(3b`G{XoCA)UHn$!e2NS0*RH*dbC4Guq{^Qo) z>1eYsY7I#c_??)&_7AL2gYqRZc3FNNG{0kh5#&oZeY{cit`IFMj(K)sd zHAqf27l3PMslb-aE#PjkIP{P^d?JR-%JwUSILc+@p@d|k>?9dAIn90VB$sO-%QyER z)F07S_1y9Iz=maUhg306dZu{Y?}BeH7oeUO5E{XdIdHPw-S+n88*p|#<9Z|;<`g*~ zH;sp&T(4O?rR3ZU`>K;`bM|`ph|IXx>;4)t>-b|t-z)beV=cVbK0?}^^%aC2)7%a3 zW6`LTxd$Pd9%cy4vcmoTJD*PAIQ`H!t&_z!vfzGf@u%9`GZ^mu$zqE1OtlW7Q@!rc zX)f1b=II`f5S%y$;C+UWgFUPM0@n?k%Np%Yd6vsYM?8kHD8pPGQN;&{0N089((`X_40S@{up0(#gNTbDmIAV~(I z0IZ%a>h%G5kp==7SeF}sQ@7&p0A;!%0Ojyz)4@Ef+b*hkp=-P$l&s) zO!z*)mV5x1z&d~hZ3I}*c7T^ZEd_oDzyfyyyhM~4S_I0A zGz9pG46gr{=G{n(eBES{} zA#)STE)GLHGpG#83}Zkkuc6bCvLbbLe7u&EQok7}E7%59xEfrKrym6qktSO+p z{3%7b_+a{Jg0^c1{zj?rpw7UbmVx|9E&nIV=Ffp1=`&j2pHehepnL|N)e596_&Lq{ zQ~IPAwcMXlv{1`Q8C--97W}fNuW9)bM&c@=0gRBd9>gKP1@3^r)~Gf*1dPEZ=`UQqT|F(@z6 zV9+C)CuK#CgR-ELT29K~DSU*13PU>)0WEg4TT>I1ior~+DdWp&o|M4~nkS`xC@9^2 z1I;%CWrY(#S>Y5={JC0c-fQ(g1YRxZ0Lqqk)3leC-v-Kz25R|G&1Y$ToTigNSC>Q`GkNn7;6=)U7lKl>7$2NWZ-BDp%R!mZ`;63-UAziBhvqX-Ms3pZ zq|9%-mVc@FuR)p5w>HgleXk>qf$}0{S02|qDJ${|XgN@UQt7VCfpSuMKv|I(e*6c@ z3RQ<5^RHo1jDJlnsHHO?r9y4Zlaj9k%C2n)%DTmCd1Fvsq||Go z&nU%3%9*l7^Q6?>^NBWFIKa>VW1Ca@cttgn$M90E$1? z5q|ualodM$ebN&;9Vz)!nkQxc=QLkJTS35@@oK0+#U>Qs`X~DD;p5-KhwI14m%i?^*(W*ELxu~XKV)6AzOUnahb6tT?TCpk!!%slIGTQ`2o1GTYcgwxeDB29>|E41$`pLj!Ne1-OXT~4n3 zs+(9Q&9BkEuhG7*eR$(>6}VA*(7ruByyTa)2krX??E{DRQ@%m_z)k(eC*G4=z)jhU z_U-lIaeD4vw66&51Gh@nD?+@6f*Qd}51S`W@PL z0PQ>A6We6Q0krQR#t+PiV6JN?z;6@!n`wscUPMLKG?K_P2f!i%ThtWQ8 zQxE&Z*K!NEDc_@g-}~@TG5344?+3IGT#>Bz1KI~})(<|hUlxIz@gv&zqfZpenLna^ zKcRi#4#*Zip?%;M{N%%n!pFePJA(Ed@rmzc!4b6YDB1_^N7?l#+6QjgQJ**>FM?Zg z4DCDS6UXGzV`$%TwC}i2oRAsE(Y_OCAGlM}Jc0Is8+*csw-{G}8+8)xJL$vAg;^)j zzEfx)xL>5_6xs)F>M5T%FSmf3avJSB?Zacr+|y{^8MF`FMOp6*+6Qjd86V!6ECM&< zEZTS0hu5EHo<;l4p?%=4$QI|&K5z@p`Gk*Jl%B8=eedp1>^FC2VW}HX+enb1fg-G)^v=7|a-+ZFHTm^2_ z1+?#iPlU>>3uxc(Xdk#r((^mo2X5-`KD@xa1>BU2Xx~Mjh>*D#(?z7*iBFHL_Xk?{ z2U_=sPejWia5Mfy>;CkKSUL00bWuef!e>?4;;(d3P0qn*oIHll>ay*nbWuYV;IpPY zhtFEF>*aJ&TQ0(99eEL-b!G1>>7t%oiqHCUg-90-=4Rlt;oP_J*+`ib#j8w`#%dKw z6BTNJB&aNsrfMBYqVkjiX{IKSB&jVRYD$0zQH=tGPb90{00`>3AtqFEf3OcH;CIr7zu*`&@v${yZl3)l11w+tPEe(dCe;Eiu%0S>#8D$^{Dht6H3c4$^ zECe4=Ft#iNJ=H1-Muk8S8v;RZl@$U(WH|`7Q_xp=%0aN1f~n;oxJ_-LU`lxi8kL72 zUFDX?N8Jh#6jP9)>Q#VXF9owIKybS%qF_cS1g%3M7^G%~LXc7sf>RU>RxK(*aFl`t z6@|~p6!Q4;erjGN2)b8-#xPY-2^w9(Ah<-q2-P(Vg5N1v76!pcb&-N4;SdZ8hagKW z4TqqAWe7qlLoixpRE8iZ0)jOZj8$d?1Rqc^HUffiY83^eA|Z&4gkXZoii9B21HpC* za+Jpd!Db4kdLWpjwoou73W7#a5KLCNQ4rLPhM<^&Tvaa`g1r>XiiY4mRYbvz7zkR& zKrmI!jDa8}7J^e0!K3OT1xu?Gu6^+5cH3OAS4cg$5log1VPmySVO@qWmbpa0}94ghu|r- zih@x!Ac(C2!5o!U1A@q!5NxMFDo;%aHd8RQCIoZU77C`+f}l|?2$agL1wq}~5EN5T zP*|_FNEYrO+_()XJl9YhG4KvpfH|j<{Vyu76`pD+I*Y=E4MYcVt@Nb;SuH9|%}x*j z?&%45w!-8u1{9u65i_pOWP6GjSIP*;P-(41ZTEv2xC5@B?PXc&%~m4FBL~?3OT+uR z&VP7)4VjheheDwO0qTh~5hm_Xi_%0T_lLufiB+UkSV;aD``@S@11-nQ3+<|T@#;n} z`EeF6ma7HrM0&{E+1B%|eBK`Zxlpth4TSijuu%t5N{DqTp`)l28S$2-rR z6Y~Lx2|d<+3S_Ks1%S`A8RnY{__JO-Sjhmcd{E|B5!joC(z~fpLjj&hCp3t%w@NKkgmX^hWZ>wdz>B!1d0orNVY{+OERe|(v0?SU990mMzpW+W#$D#uq23S5LK_Co1LDN3e&EdPzq$0N+!~_tbqk2Wl z8bP)WGFsuQkTE(Q*oXj)aH-bA>oe91B{agn#M`FsHH#C|e z9I9n}O9OxW&n;JFEpy(~Xa*VIy5MUvZ)-hHp^pHL9j%&an*(dKY$e}LK|Yp+u1A2a zd>>kr(L&jJwsMtDObca8DO;^&v`~&IFIIp-T4)t!g6ktKqlF#?SSh~!!D!24eTD#Q z^$CPD7FsO}=Ct`#C#Kc1V9M5OSsR4;nhPtsLCe}AOv7QN_$mqhEUV?K0IbwT2v~4? z-~$wf_TwR>R;1OQ0$9_nT1Klqt!3LFV+CooESMGDuJvfO3lVM$`UNQUXtjJLjfL&d zdR-A-j4<_g3fEw(Hniby04sr3qz$tdnBZ$IqYbkcDBGiD-4SMGsQ-o0rAk`s}psBdH|P;U(q4wf!}}&0GEr409S#5zz~3|#c9a!o`~W4 zQN8!P7~HNd_=Xd3qh>hLD8 z4B)fdX8{Gw17-q`sig}uEI zsecXZ0lop=0agMZ0Q5F1fVcQuo$n+(4)9Hehk-``z7{bRm;j6co`N!$3%;i{28ElT z8&MIyZ1g6u40sDT2HoQTAM_st4grUO?*Tr{p9RbS9tHA&2Ut)xFc!EQ;8LRix*{$o z^MN_Q(|`ny11A8wrMpp)aX>KmGC)~?Z#S(5J_J4j)&OgPkAY8sb-1>^v)Bhxnk&cy}53ji0%Lg>-;Y=`a_03AISuerbu@br(}5N-p^ z1n6f?d0=S5m1LfjU0pNmC z5vT-kA)!8fH+^+Da3}JJ2dxhbL3kKY1K}z_2Evs=%L4(xpWrV69{?ZHqjJUJ3bO)u z2Ur5U4lD+y0`$4h0nY&QfTw_&z;s|TFdi5Q^Z~di(F=0PrRUqOE6WPkL^u}UYhh79 zG|-kS8dsV1zy^Sei+=`mm9GI@B&s99NyO9V)5mWFJ_j6~rO=^I_b-6{{0~1}>MR22 z)9J!ru7!Y8+r8Iiw&Nv4;+nUTez|rY?UeRPyQNKz1!%`Kh2g+3fKDw5hybP`o;}F! zF9&d_!hjHfHcVTlO>>fMhTdPm{Q!rEjk^Hs1ZZZPXhLjl5HK3p0^C=4+e_lLfILUW zOqh_FP~8}c`Uik~0J?dW zlB4hlg#HS&pB#W05f1|o0S^NC01GES1IP!c|0wV#z&ss&=J_Nr3wQ!}9C!?Plqs?h zm2Yv;9295(qfuDdMf&IV_s?i&wM&3RIi-5hrZeSO{qR0;gDgu;!4N%S^SQKT9 zbL8Y1?=SzxPewiJQ-|rvqwGBEe@UFg%$#+jB4tkIPJ!gZ0Vk7UgsTA5Wg18SpYk~X zz3L@tFm3!3F%wqc`oa%ER{1(P3vu*32s@Sf9%1ImILe&L)YdY_h3mLIDBmiuu3w~+ zh>9Gs`Z~^E&deGDM|7Nj1(=Q_&I%j@oOHAdr@|F=xZXJg&H^U@|4e#;zZEzGoCHp* z;#Wk3IHl$+6W%;(cL06`egT~9STu`sit?AUx*0nDH&EDjNk2Iyv#*$klg^QI=FkZK z07lVjE+X(dz^OUX7M9F&XC5=2(}`1!dd@WaM9ZAHe+j&k#_=2eQ_)Fxb*f|3uAl3d zk;(Oi(YKWr*ysZRKq>z0d3YR7WiN90GvD-^~8F+eoXOdVP- z((?u(*cE6Gv;(-^Ndb}p?hCy@8qgZxiKivd3P=Sy0v&)(Kxcr5zWzX8pbyX+=mqou zZUKA%_nX~;p1`fVkCu+WZ9qRw8F4$10So{J0z-g1fWbf}FccUAaFfeUy%QJ#aOaW* zEJFw11v&~Csd-1vIPTJ!eh&9=xd_}3JPJGvJOogA3XlUZK{n`kU>tBaFc#pM)Jc0E z!jpk}fJuN;7}HDymlLHVwG!Mq5%93Tw_sPq8j(}1Z!KER{ggPKnFllfPa z6?qeB9|2`Wm?vAx{APnb1w01K0N99rGx6~xFbjABcpPX2Wts^Up9WZ%Q?S!@_dtiU zEe99{DClfni4R-@qtuh{h}wl8ydw^lN`O?}d$YIag2f8im{`C*gYZ~7qJ_&=b5(-74 zSfECbyr~KbMFZ0lV1FxCmRhu1cq%zxg?eT_teEgkt#N;{pq` zMYtQKy3VVoJ`|p4BM|cv_y9>7EALUAq`lfnNk4UN zJ5oAdXgT<~=Z>v75j&D86RniFs_H7?NpiloZf5S48jaPczpg60tPA~iO8W+-jm>u% zA~8P2N?NGKBB|J^8m$NUK`kP8S*=@#0-bL^J-^iuy^iU34xRol)%pracr@fkR~haKfQDh|D-RaAwKM3kehbMsg5 zsB)i*X0VAvA6aSEeF38NtU^5EtV-B`s+?C7P9v_lQfox_Xy+SfU#|b%jvvd zg!MFm!?vqz7xE9CF$2HZtZUAkSq?^wS=c{`^YcU{yTG-(LLo|&~qG!3Tcd1@yO_!<*4Lc`QsTSTOYQB$|V@EWQ| zH;R#X^m}F_RHvxC-$7=owwpzmO5P-@Mmygn+F*!SjK=?eYB#fyXfY6gFWHV~)d=+7j#-A?sY+WCQESuTpZ6(urRjz^* z&KC?1olvuNn=<87v>!!(WvQo-SB&!w!I8^XSLohqR7)r%z#%uqp2qX+Hng*nDrT9^ zHwe$aGk14E`A?Q=Jrt3qLbssCJyipeA?kr`AUSFsaSgmw%pkVcu6)V`9}BE?0G-5C(-SrS2?v<)ia%TLDAv zvez@riZVC#vU*nk7jx^HIr9&zYnqz#CHg~q2LHyEIG`KGX{Tzh=h3ZeSjVCK-^v6T zMG0O1pgT&j+>uWFPu}s`LUqP<)uBCDLz|*snk%sjRlHS2lVq#6BaO<+w@bug^O3bn zG>D#A(>g*qUoo9ODyrFq zm{SSQ9uN@`*lKb6$&JpsVdZD9X`TE+n5bM|z3`QYtXdUWVGo+mtsu8vqjI;b{o}n~ zc2|G~?gA;XTZF6QUx{Ysc<|xSuxqZy9Tx6r=i777RbP4ku*{d=H^c^LF`s?v_OH=# z=T-MBnDDL!YWCM6(gv%eA5gw?2$+=i-;4Dl{N~Z3` zYbWc~sUnDWs4o!{bFh*11T{Z9{^zE{uJl`h+R#@0Y7)v zh<3gR`;MZs@jVts%|u4Y@d?f2lU=$6*OaSUAhxMg^hvbyEwd+%&Y$16@%me=K1t$? zIjhF8)41B0=-VGwbe^|z-9sHLg#;>uG*K__6HV=Y;tWZ0O;+djiNt8z+xu zmv4_nnx^qBm}Zyq?nf7Vt1|YBM598YdUC&rjCQ`#d*Az`mqp}GdlmY68GB2uqyB2O zpJanNk7QUN6Dk^Es`a;`DmD~@zC|;fZz;cbf7J?~4{MaGYuOy<5;X@3M$P8x&2O=Y z-J=q|!-8Cdv`K$I95}_^U|mhbN{z8Os~Q!fCRu`-3Knl`O^NK{a@eF_171F zGz3>po*4pLs1?N`GRgTe^OL_1EgoDcsM*yPI$vvkqHHx!nd!BfB1Z2}r>mWn3{SN4 zHRiQGd$n(!=)17b!SFU^Z(D8gi0_r}J5)&hO0W#da@t1Z?iy5%`WC2VjPJhEF{pS~y|OcD$yw^8mWnZ@Shm z9VV`wqqiKqevXFOuHs;ty#y`mdGBYLZJxyD5%o*J@v!-?YSF>J`3N=SGB#U|d)RSB z+~Ma8u5|)-0m7QJiL4#8<5l&;qREZ)uKFOm9rgc1zA??(Soan4JrDP9AM3sYi>q}g zh2Q?0SL@bBwK1?x4E$ci=x*}Q{%<~PpE~rt=x)?%tD5`(!)e~ux~rC7RC?Bt^ham$ zn2Z6WZQVRZj}^x!=I`FPTswPXaJ}Dk#!!3PJHu*05jO?%D=zA_;zHn+7GWZ$oqFa+ zacfdD#FRzM;;+Wned_nn(RfP@lY`6F2%Y)Ndm9ayJosG2Fwq8*TF^bOVt*1H|JGc; zx~dm`g7@v+U~Us5Qf>W7L?#uudC}{k1%T zEV{Q>J&uT$(dBTfldfyuF4qq)HM#YG9woRT+p>*_4r=WYcrc6Q$qjb^;K;1y7l(Y0 zvmfiHLjD3bT+N~|S1tMj1$& z@hH&@3UgG*8RTaj;=|pPY*u4WL1NwO@EAs*y8k$wuJii=xA#1`IP8g;tLz#lL;s9g zbPC>CD+;|cE7=_-oIqxUD)cNIp#RC;BO5m>4X2G z7<=r*Cg+@g^UQQ*b)Np!E4jg7-Jan}r0=v?nO!?9{^_ney*g&6JNt$`R&qSQnzc1t zC7q+IS8})Ds$NO2==|+T@Q-&3XmgG|=~TJ``W%GmcOIj!8+FaO!Zt@|GCC?t)u7+5 z=6U^D#WJO9jiR4wbODBS!}|c%_Qg7aUf+|}`M+ThS5~-|h6+c{Fctc{=zHUP0n2{J zwq>r`e)aZ%-{IaP?34ZfBS%)dz0o&6Z);z;T75{Dcx`WLTP!)#`dj<8mRT~uJk#Rb zX?fd{+ed3iPT=I+-2u9U~en*cEQ??IIE_5_Oj(Pwp|tt{%Mi>hvU%= z&qd)0&7GemxqE;3ll@y=I)~d*YS|SLr?)kC=q1zrTyN)ac9RN}#;&os%Ubs%jj#rD zR~t>8>gz?nu4*ETno-wp8Y+l0$eViswc@e}4@^Ne=teB)9`#P4wP@eqAQl#g+gk~V zg$l-ZZc&Pbk*jG@?q9 z%lTcFEeE59xDKEA#Sm}wwYOEyzJbR~>yXS6_m(nR+S}>MZp>&~Crq^qFsg`MDl@?F zh|}sm5Tk5A^=yD~izGG_1dUXjV`zzqaEB5KNzM<)EM7Y8uG2xac3oA_zb%vh zMyISn+1@(OUyZq-LQKQc-1#w@`W5RYipRfSbXCFmxtcLU7GK#jXI0y)F)0JpKoj+M ze&FWFvlD+_dB|mob_qIX{C_)i``d@6)I8Aj;Cj_RRTVoQ#i8yPyB=6n2i*eOGM6E~}|! z&`HiO=4_dDf4KnYr*S%)}R93G*U!L01pds~mp3h!=0QfCz!sXi}b+!^is)=jTz zV?O%!?Uipr12?p}PR&-`%c7;uPvpGv>qUZ-nDK-2}HwtOIv=RR*1(afk*N&=nCD^`cssX>TNy1pG1^nf&KV*l)oE8lhJ z+6|{>Pxuz~!m|PPk#)brW7N%7w^W2lrDUsv6^zIv=XZEA?tOX9`S9=Fa=N66%lW0A z&WFcztiP@hTYEN>_({Bq3{9Uskxy>N1kdzjoa)yEhQyK zJ3l3K;ra519{FJFEIVJ+vdKLv4>!(|oZlpBx$5y-{|q1gj-!xN@~cJV-?;qdr1>?= zIWf(hD*097*-Az;yHXLQlI&4oKv&vNQP=M#Ly9A5lBI0j{W)35Xlmd6@u-wABP#ml zcYpj#ctIVCFv0@acdqx;qA=L1^Fv8Px(+K-edcZZ&=lOJt3{yf3^Elusf~q-JJrY1+fI zdaGG{Qex7(YA_Z;{D)d@gpsJ1#pvrxsvsIpwGaB%VEK3MO~%0)D(1{t`8V%!byiyU z{3|ONX~f#M)Vl{Si863V#{V2dt00fj28`FYDuKy1;rNL-l{o Hc>R9?5v20~ delta 30234 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 Ak^lez diff --git a/firestore.rules b/firestore.rules index e24b62e..258ca84 100644 --- a/firestore.rules +++ b/firestore.rules @@ -25,6 +25,10 @@ service cloud.firestore { allow read: if authenticated() && isAdmin(eventData(eventId)); allow write: if authenticated() && isAdmin(eventData(eventId)); } + match /team/{memberId} { + allow read: if authenticated() && isAdmin(eventData(eventId)); + allow write: if authenticated() && isAdmin(eventData(eventId)); + } match /schedule/{scheduleId} { allow read: if authenticated() && isAdmin(eventData(eventId)); allow write: if authenticated() && isAdmin(eventData(eventId)); diff --git a/package.json b/package.json index 89e6e5f..33e428a 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.47.0", "react-hook-form-mui": "^6.5.4", + "react-hotkeys-hook": "^4.4.1", "react-redux": "^8.1.3", "use-firebase-auth": "^3.0.0", "uuid": "^9.0.1", diff --git a/src/components/form/SaveShortcut.tsx b/src/components/form/SaveShortcut.tsx new file mode 100644 index 0000000..4190334 --- /dev/null +++ b/src/components/form/SaveShortcut.tsx @@ -0,0 +1,16 @@ +import { useHotkeys } from 'react-hotkeys-hook' +import { useRef } from 'react' + +export const SaveShortcut = () => { + const ref = useRef(null) + useHotkeys(['ctrl+s', 'meta+s'], (event) => { + event.preventDefault() + // @ts-ignore + const form = ref.current?.closest('form') + if (form) { + form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true })) + } + }) + + return
+} diff --git a/src/events/actions/getTeam.ts b/src/events/actions/getTeam.ts new file mode 100644 index 0000000..3ba9dba --- /dev/null +++ b/src/events/actions/getTeam.ts @@ -0,0 +1,11 @@ +import { TeamMember } from '../../types' +import { getDocs } from 'firebase/firestore' +import { collections } from '../../services/firebase' + +export const getTeam = async (eventId: string): Promise => { + const snapshots = await getDocs(collections.team(eventId)) + + return snapshots.docs.map((snapshot) => ({ + ...snapshot.data(), + })) +} diff --git a/src/events/actions/updateWebsiteActions/generateStaticJson.ts b/src/events/actions/updateWebsiteActions/generateStaticJson.ts index 151902f..f59a76e 100644 --- a/src/events/actions/updateWebsiteActions/generateStaticJson.ts +++ b/src/events/actions/updateWebsiteActions/generateStaticJson.ts @@ -3,11 +3,13 @@ import { getSessions } from '../sessions/getSessions' import { getSpeakers } from '../getSpeakers' import { getSponsors } from '../getSponsors' import { generateOpenFeedbackJson } from './generateOpenFeedbackJson' +import { getTeam } from '../getTeam' export const generateStaticJson = async (event: Event) => { const sessions = await getSessions(event.id) const speakers = await getSpeakers(event.id) const sponsors = await getSponsors(event.id) + const team = await getTeam(event.id) const openFeedbackOutput = generateOpenFeedbackJson(event, sessions, speakers) @@ -85,6 +87,7 @@ export const generateStaticJson = async (event: Event) => { })), sessions: outputSessions, sponsors: outputSponsor, + team: team, generatedAt: new Date().toISOString(), } const outputPrivate = { @@ -92,6 +95,7 @@ export const generateStaticJson = async (event: Event) => { speakers: speakers, sessions: outputSessionsPrivate, sponsors: outputSponsor, + team: team, generatedAt: new Date().toISOString(), } diff --git a/src/events/page/EventRouter.tsx b/src/events/page/EventRouter.tsx index 5f43cbf..6e41f7d 100644 --- a/src/events/page/EventRouter.tsx +++ b/src/events/page/EventRouter.tsx @@ -8,9 +8,6 @@ import { FirestoreQueryLoaderAndErrorDisplay } from '../../components/FirestoreQ import { Event } from '../../types' import { SuspenseLoader } from '../../components/SuspenseLoader' -const EventSponsors = lazy(() => - import('./sponsors/EventSponsors').then((module) => ({ default: module.EventSponsors })) -) const EventSettings = lazy(() => import('./settings/EventSettings').then((module) => ({ default: module.EventSettings })) ) @@ -27,9 +24,16 @@ const EventSpeakers = lazy(() => const EventSpeaker = lazy(() => import('./speakers/EventSpeaker').then((module) => ({ default: module.EventSpeaker }))) const NewSession = lazy(() => import('./sessions/NewSession').then((module) => ({ default: module.NewSession }))) const NewSpeaker = lazy(() => import('./speakers/NewSpeaker').then((module) => ({ default: module.NewSpeaker }))) +const EventSponsors = lazy(() => + import('./sponsors/EventSponsors').then((module) => ({ default: module.EventSponsors })) +) const NewSponsor = lazy(() => import('./sponsors/NewSponsor').then((module) => ({ default: module.NewSponsor }))) const Sponsor = lazy(() => import('./sponsors/Sponsor').then((module) => ({ default: module.Sponsor }))) +const EventTeam = lazy(() => import('./team/EventTeam').then((module) => ({ default: module.EventTeam }))) +const NewMember = lazy(() => import('./team/NewMember').then((module) => ({ default: module.NewMember }))) +const EventMember = lazy(() => import('./team/EventMember').then((module) => ({ default: module.EventMember }))) + export const EventRouter = () => { const [_, params] = useRoute('/events/:eventId/:subRoute*') const event = useEvent(params?.eventId) @@ -71,6 +75,24 @@ export const EventRouter = () => { + + }> + + + + + + }> + + + + + }> + + + + + }> diff --git a/src/events/page/EventScreenMenuItems.tsx b/src/events/page/EventScreenMenuItems.tsx index b0f1b7a..c42f224 100644 --- a/src/events/page/EventScreenMenuItems.tsx +++ b/src/events/page/EventScreenMenuItems.tsx @@ -1,7 +1,14 @@ import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material' import * as React from 'react' import { Icon } from '@mdi/react' -import { mdiAccountVoice, mdiCalendarWeekend, mdiCashMultiple, mdiCogBox, mdiPresentation } from '@mdi/js' +import { + mdiAccountMultiple, + mdiAccountVoice, + mdiCalendarWeekend, + mdiCashMultiple, + mdiCogBox, + mdiPresentation, +} from '@mdi/js' import { useRoute } from 'wouter' import { LinkBehavior } from '../../components/CCLink' @@ -26,6 +33,11 @@ export const Menu = [ icon: mdiCashMultiple, name: 'Sponsors', }, + { + href: '/team', + icon: mdiAccountMultiple, + name: 'Team', + }, { href: '/sessions', icon: mdiPresentation, diff --git a/src/events/page/sessions/EventSessionForm.tsx b/src/events/page/sessions/EventSessionForm.tsx index 25da1b1..627d925 100644 --- a/src/events/page/sessions/EventSessionForm.tsx +++ b/src/events/page/sessions/EventSessionForm.tsx @@ -14,6 +14,7 @@ import { useSpeakers } from '../../../services/hooks/useSpeakersMap' import LoadingButton from '@mui/lab/LoadingButton' import { ImageTextFieldElement } from '../../../components/form/ImageTextFieldElement' import { useLocation } from 'wouter' +import { SaveShortcut } from '../../../components/form/SaveShortcut' export type EventSessionFormProps = { event: Event @@ -287,6 +288,7 @@ export const EventSessionForm = ({ event, session, onSubmit }: EventSessionFormP + ) } diff --git a/src/events/page/settings/EventSettings.tsx b/src/events/page/settings/EventSettings.tsx index 79ff1e3..ea03804 100644 --- a/src/events/page/settings/EventSettings.tsx +++ b/src/events/page/settings/EventSettings.tsx @@ -26,6 +26,7 @@ import { useFirestoreDocumentDeletion, useFirestoreDocumentMutation, } from '../../../services/hooks/firestoreMutationHooks' +import { SaveShortcut } from '../../../components/form/SaveShortcut' const schema = yup .object({ @@ -172,6 +173,7 @@ export const EventSettings = ({ event }: EventSettingsProps) => { + {!deleteOpen && !reImportOpen && } diff --git a/src/events/page/speakers/EventSpeakerForm.tsx b/src/events/page/speakers/EventSpeakerForm.tsx index f829397..21f51e6 100644 --- a/src/events/page/speakers/EventSpeakerForm.tsx +++ b/src/events/page/speakers/EventSpeakerForm.tsx @@ -5,6 +5,7 @@ import { Grid } from '@mui/material' import LoadingButton from '@mui/lab/LoadingButton' import { SpeakerSocialFields } from './SpeakerSocials' import { ImageTextFieldElement } from '../../../components/form/ImageTextFieldElement' +import { SaveShortcut } from '../../../components/form/SaveShortcut' export type EventSpeakerFormProps = { event: Event @@ -165,6 +166,7 @@ export const EventSpeakerForm = ({ speaker, onSubmit, event }: EventSpeakerFormP + ) } diff --git a/src/events/page/sponsors/components/SponsorForm.tsx b/src/events/page/sponsors/components/SponsorForm.tsx index abd04fa..2acdc98 100644 --- a/src/events/page/sponsors/components/SponsorForm.tsx +++ b/src/events/page/sponsors/components/SponsorForm.tsx @@ -4,6 +4,7 @@ import { FormContainer, TextFieldElement, useForm } from 'react-hook-form-mui' import { Grid } from '@mui/material' import LoadingButton from '@mui/lab/LoadingButton' import { ImageTextFieldElement } from '../../../../components/form/ImageTextFieldElement' +import { SaveShortcut } from '../../../../components/form/SaveShortcut' export type SponsorFormProps = { event: Event @@ -92,6 +93,7 @@ export const SponsorForm = ({ event, sponsor, onSubmit }: SponsorFormProps) => { {sponsor ? 'Save' : 'Add sponsor'} + ) } diff --git a/src/events/page/team/EventMember.tsx b/src/events/page/team/EventMember.tsx new file mode 100644 index 0000000..2bf90db --- /dev/null +++ b/src/events/page/team/EventMember.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import { Box, Button, Card, Container, Link, Typography } from '@mui/material' +import { ArrowBack } from '@mui/icons-material' +import { useLocation, useRoute } from 'wouter' +import { doc } from 'firebase/firestore' +import { collections } from '../../../services/firebase' +import { FirestoreQueryLoaderAndErrorDisplay } from '../../../components/FirestoreQueryLoaderAndErrorDisplay' +import { MemberForm } from './components/MemberForm' +import { useFirestoreDocumentMutation } from '../../../services/hooks/firestoreMutationHooks' +import { useTeam } from '../../../services/hooks/useTeam' +import { Event, TeamMember } from '../../../types' + +export type EventMemberProps = { + event: Event +} +export const EventMember = ({ event }: EventMemberProps) => { + const [_, params] = useRoute('/:routeName/:memberId*') + const members = useTeam(event.id) + const [_2, setLocation] = useLocation() + + const memberId = params?.memberId + + const mutation = useFirestoreDocumentMutation(doc(collections.team(event.id), memberId)) + + if (members.isLoading || !members.data) { + return + } + + const member = members.data.find((s: TeamMember) => s.id === memberId) + + if (!member) { + setLocation('/team') + } + + return ( + + + + + + {member.name} + + { + await mutation.mutate(data) + }} + /> + + + ) +} diff --git a/src/events/page/team/EventTeam.tsx b/src/events/page/team/EventTeam.tsx new file mode 100644 index 0000000..de8e0b3 --- /dev/null +++ b/src/events/page/team/EventTeam.tsx @@ -0,0 +1,38 @@ +import { Event, TeamMember } from '../../../types' +import * as React from 'react' +import { FirestoreQueryLoaderAndErrorDisplay } from '../../../components/FirestoreQueryLoaderAndErrorDisplay' +import { Box, Button, Card, Container, Typography } from '@mui/material' +import { useTeam } from '../../../services/hooks/useTeam' +import { Member } from './components/Member' + +export type EventTeamProps = { + event: Event +} +export const EventTeam = ({ event }: EventTeamProps) => { + const team = useTeam(event.id) + + const teamData = team.data || [] + + if (team.isLoading) { + return + } + + return ( + + + {team.data?.length} members + + + + + + {teamData.map((member: TeamMember) => ( + + ))} + + + + + + ) +} diff --git a/src/events/page/team/NewMember.tsx b/src/events/page/team/NewMember.tsx new file mode 100644 index 0000000..939f890 --- /dev/null +++ b/src/events/page/team/NewMember.tsx @@ -0,0 +1,41 @@ +import * as React from 'react' +import { useLocation } from 'wouter' +import { collections } from '../../../services/firebase' +import { Button, Card, Container, Typography } from '@mui/material' +import { ArrowBack } from '@mui/icons-material' +import { Event } from '../../../types' +import { MemberForm } from './components/MemberForm' +import { useFirestoreCollectionMutation } from '../../../services/hooks/firestoreMutationHooks' +import { slugify } from '../../../utils/slugify' + +export type NewMemberProps = { + event: Event +} +export const NewMember = ({ event }: NewMemberProps) => { + const [_, setLocation] = useLocation() + const mutation = useFirestoreCollectionMutation(collections.team(event.id)) + + return ( + + + + New member + + { + return mutation.mutate(member, slugify(member.name)).then(() => { + setLocation('/team') + }) + }} + /> + + {mutation.isError && ( + Error while adding member: {mutation.error?.message} + )} + + + ) +} diff --git a/src/events/page/team/components/Member.tsx b/src/events/page/team/components/Member.tsx new file mode 100644 index 0000000..79e8466 --- /dev/null +++ b/src/events/page/team/components/Member.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import { Avatar, Box, IconButton, Link, Typography } from '@mui/material' +import { DeleteRounded } from '@mui/icons-material' +import EditIcon from '@mui/icons-material/Edit' +import { Event, TeamMember } from '../../../../types' +import { useFirestoreDocumentDeletion } from '../../../../services/hooks/firestoreMutationHooks' +import { collections } from '../../../../services/firebase' +import { doc } from 'firebase/firestore' + +export type MemberProps = { + event: Event + member: TeamMember +} +export const Member = ({ event, member }: MemberProps) => { + const deletion = useFirestoreDocumentDeletion(doc(collections.team(event.id), member.id)) + + return ( + + + + {member.name} + + + + + + { + await deletion.mutate() + }} + edge="end"> + + + + + ) +} diff --git a/src/events/page/team/components/MemberForm.tsx b/src/events/page/team/components/MemberForm.tsx new file mode 100644 index 0000000..82b4ef3 --- /dev/null +++ b/src/events/page/team/components/MemberForm.tsx @@ -0,0 +1,105 @@ +import * as React from 'react' +import { Event, TeamMember } from '../../../../types' +import { FormContainer, TextFieldElement, useForm } from 'react-hook-form-mui' +import { Grid } from '@mui/material' +import LoadingButton from '@mui/lab/LoadingButton' +import { ImageTextFieldElement } from '../../../../components/form/ImageTextFieldElement' +import { SaveShortcut } from '../../../../components/form/SaveShortcut' + +export type MemberFormProps = { + event: Event + member?: TeamMember + onSubmit: (member: TeamMember) => void +} +export const MemberForm = ({ event, member, onSubmit }: MemberFormProps) => { + const formContext = useForm({ + defaultValues: member + ? member + : { + name: '', + photoUrl: '', + role: '', + }, + }) + const { formState } = formContext + const isSubmitting = formState.isSubmitting + + return ( + { + if (member) { + return onSubmit({ + id: member?.id, + name: data.name, + role: data.role, + photoUrl: data.photoUrl, + } as TeamMember) + } + return onSubmit({ + name: data.name, + role: data.role, + photoUrl: data.photoUrl, + } as TeamMember) + }}> + + + + + + + + + + + + + + {member ? 'Save' : 'Add member'} + + + + + + ) +} diff --git a/src/services/converters.ts b/src/services/converters.ts index 3b5e610..d0f5c0d 100644 --- a/src/services/converters.ts +++ b/src/services/converters.ts @@ -1,4 +1,4 @@ -import { Event, NewEvent, Session, Speaker, SponsorCategory } from '../types' +import { Event, NewEvent, Session, Speaker, SponsorCategory, TeamMember } from '../types' import { FirestoreDataConverter } from '@firebase/firestore' import { DateTime } from 'luxon' @@ -74,3 +74,17 @@ export const sponsorsConverter: FirestoreDataConverter = { + fromFirestore(snapshot): TeamMember { + const data = snapshot.data() + + return { + id: snapshot.id, + ...data, + } as TeamMember + }, + toFirestore(member: TeamMember) { + return member + }, +} diff --git a/src/services/firebase.ts b/src/services/firebase.ts index def9957..158dc7b 100644 --- a/src/services/firebase.ts +++ b/src/services/firebase.ts @@ -4,7 +4,7 @@ import { FirebaseApp } from '@firebase/app' import { getAuth } from 'firebase/auth' import { getStorage } from 'firebase/storage' import { Auth } from '@firebase/auth' -import { eventConverter, sessionConverter, speakerConverter, sponsorsConverter } from './converters' +import { eventConverter, sessionConverter, speakerConverter, sponsorsConverter, teamConverter } from './converters' const config = { apiKey: import.meta.env.VITE_FIREBASE_OPEN_PLANNER_API_KEY, @@ -30,6 +30,7 @@ export const collections = { collection(instanceFirestore, 'events', eventId, 'sessions').withConverter(sessionConverter), speakers: (eventId: string) => collection(instanceFirestore, 'events', eventId, 'speakers').withConverter(speakerConverter), + team: (eventId: string) => collection(instanceFirestore, 'events', eventId, 'team').withConverter(teamConverter), } export const getOpenPlannerAuth = (): Auth => { diff --git a/src/services/hooks/firestoreMutationHooks.ts b/src/services/hooks/firestoreMutationHooks.ts index 6463490..62710ab 100644 --- a/src/services/hooks/firestoreMutationHooks.ts +++ b/src/services/hooks/firestoreMutationHooks.ts @@ -1,11 +1,11 @@ -import { addDoc, CollectionReference, deleteDoc, DocumentReference, setDoc } from 'firebase/firestore' +import { addDoc, CollectionReference, deleteDoc, doc, DocumentReference, setDoc } from 'firebase/firestore' import { useCallback, useState } from 'react' export type UseMutationResult = { isLoading: boolean error: { message: string } | null isError: boolean - mutate: (data: object) => any + mutate: (data: object, id?: string) => any } export const useFirestoreCollectionMutation = (ref: CollectionReference): UseMutationResult => { @@ -13,10 +13,16 @@ export const useFirestoreCollectionMutation = (ref: CollectionReference): UseMut const [error, setError] = useState(null) const mutate = useCallback( - async (data: any) => { + async (data: any, id?: string) => { try { setLoading(true) + if (id) { + await setDoc(doc(ref, id), data, { merge: true }) + setLoading(false) + return id + } const newRef = await addDoc(ref, data) + setLoading(false) return newRef.id } catch (error: any) { setError(error) diff --git a/src/services/hooks/useTeam.ts b/src/services/hooks/useTeam.ts new file mode 100644 index 0000000..442d2cf --- /dev/null +++ b/src/services/hooks/useTeam.ts @@ -0,0 +1,8 @@ +import { collections } from '../firebase' +import { DocumentData, query } from '@firebase/firestore' +import { useFirestoreCollection, UseQueryResult } from './firestoreQueryHook' +import { TeamMember } from '../../types' + +export const useTeam = (eventId: string): UseQueryResult => { + return useFirestoreCollection(query(collections.team(eventId)), true) +} diff --git a/src/types.ts b/src/types.ts index 0766b82..ce2f950 100644 --- a/src/types.ts +++ b/src/types.ts @@ -134,6 +134,13 @@ export interface SponsorCategory { sponsors: Sponsor[] } +export interface TeamMember { + id: string + name: string + role: string + photoUrl: string | null +} + export interface ConferenceHallEvent { id: string name: string