From 0978c80145d76b9920fe8abd87c3cadfbd7df771 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:54:08 +0000 Subject: [PATCH] [jules] enhance: Add Error Boundary with dual-theme support --- .Jules/changelog.md | 1 + .Jules/knowledge.md | 24 ++++++++ .Jules/todo.md | 10 ++-- verification_error_boundary.png | Bin 0 -> 18430 bytes web/App.tsx | 5 +- web/components/ErrorBoundary.tsx | 92 +++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 verification_error_boundary.png create mode 100644 web/components/ErrorBoundary.tsx diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..c3bb655 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,7 @@ ## [Unreleased] ### Added +- Error Boundary (`ErrorBoundary`) wrapping the web application to catch runtime errors gracefully. Includes a dual-themed `ErrorFallback` UI with "Try Again" and "Back to Home" actions. - Inline form validation in Auth page with real-time feedback and proper ARIA accessibility support (`aria-invalid`, `aria-describedby`, `role="alert"`). - Dashboard skeleton loading state (`DashboardSkeleton`) to improve perceived performance during data fetch. - Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users. diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md index d69c659..518396e 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -619,6 +619,30 @@ _Document errors and their solutions here as you encounter them._ --- +### ✅ Successful PR Pattern: Error Boundary Implementation + +**Date:** 2026-01-14 +**Context:** Adding robust error handling to Web App + +**What was implemented:** +1. Created `ErrorBoundary` Class Component (required for `getDerivedStateFromError`). +2. Created `ErrorFallback` Functional Component (to use `useTheme` hook). +3. Wrapped `AppRoutes` in `App.tsx` (inside `ThemeProvider`). +4. Provided "Try Again" (reload) and "Back to Home" (href redirect) options. + +**Why it succeeded:** +- ✅ Separation of concerns: Class for logic, Function for UI/Hooks. +- ✅ Full dual-theme support (Neo/Glass) in fallback UI. +- ✅ Safety: `window.location.reload()` clears bad state reliably. +- ✅ Integration: Placed correctly within Provider hierarchy. + +**Key learnings:** +- React Error Boundaries *must* be class components. +- Use a child functional component if you need Hooks (like `useTheme`) in the error UI. +- `window.location.href = '/'` is safer than `navigate('/')` for "Back to Home" when the app is in an undefined state. + +--- + ## Dependencies Reference ### Web diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..421cd0f 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -34,12 +34,10 @@ - Impact: Guides new users, makes app feel polished - Size: ~70 lines -- [ ] **[ux]** Error boundary with retry for API failures - - Files: Create `web/components/ErrorBoundary.tsx`, wrap app - - Context: Catch errors gracefully with retry button - - Impact: App doesn't crash, users can recover - - Size: ~60 lines - - Added: 2026-01-01 +- [x] **[ux]** Error boundary with retry for API failures + - Completed: 2026-01-14 + - Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx` + - Impact: App doesn't crash completely on runtime errors; users can recover via retry or home button. ### Mobile diff --git a/verification_error_boundary.png b/verification_error_boundary.png new file mode 100644 index 0000000000000000000000000000000000000000..f32f4f5ef1c343b3f12d47f6e8dda817e41c6f2e GIT binary patch literal 18430 zcmeIacT`hpyEhzUgmDlUD@cou1(m8)r8ySRL5k9)1nE5@B|sp7aRdbcQ30tD5s(&o zLQPO0k={Eb^cn~RNPx6&^OW{sKcb)b9^StkSSSuteJA3czzOVNCUH5hG*T#l= zg1?CU0s?^qAKbtD7z8>Dd_2_i^C95v7WfYy5a?IXgS)?*2BlLb1wzRIVM|%Hrhxh(0cZso(d-yYG_s?RSVwqper?b_Sz? zMe}SeL{XhlooQtyTlB`wv1y|G`h6XL>bk5G)DprF-J6nwo#Pr~G9=2|iYv8OU@qx4*jX@jTzP+wG=ja7)ZpggYRERm`?(!P^MG*J?5akdkR*oxBL z&T@>`QnkMZNX9z;H|b;pg*7)C6D{oaU`Ph6TZ=JK&YtO*)vIHC5+M`??OJ4CWW}Ng zC6SKQ1_o~Koxb+&H(~UXs;l&Xi2=hJ)J=fT|8xBQ|8bT9p0h~;@#2*Ub>(PQLVkX_h9yto({b% zUW>Ck%cMwhXH_;u76}m*%5!y&qv0F;=-BQsh0Mv5W@cswgRdjA*V*)O1^dlk|0T{% z*kr))DulbAm6b&abo370hRt5&CvH*Vb~bS(%~jY1EM&hgndIMZ(Bg2-KH)!|Do|=I#<1W9>TL6`#)V@EthhLM+H-x;--V zKuuMi!>8%7NT1ggkkbM^D!jehHKK@1h(#D;Q3kTtR_@$>==4=UKn36>&eL%yc@S!A zJK2q(SG=$jRjXIBvwCybb5hHfE^FCt3nO!5cdnIQ>$D52Sd8=P2lX(4q!;FBTf=IEvVeg%Ka|w|r;3 zd$DEKX}(JkhkAK?3xe!5fGmp%*cr6oc9L10BzQ{|@+7h7P0P$kVlUjCQguOl^R*F+ zP3DGyK-PXo6&0ZpydB>w1J}lsy3`%}a}5AJ?9T36`fKkfY@!ufaLje)8WhRg?l%&) zyf4A~uVE@HEBp67kUp@U(#V28D7xWQUPT}DCJP+pU(juN%(bKOH=jU?@io{KJOsB zFT4IDsW>)fZ*d(w}wegO%3030qhTQ2(ZCJz~zA8RWltazOdcV&Cn7np}rX( zhB9{_M75}qB%@?wZqUdaRSv>79YzXnXlUTsGWeIX-U?;Zk;B$JA!&lGq%DihnN!z9 zRJBi(u#B~(H1aJZjK5p1lbIa8C+k#w7imb8gW+fqJjr9j{;Zx|Vxkcm-JX=#c)UhL>$-FtIa7&yDCo>O$`VM41@R^@=<2be$$OI$n2d?{N z7KHHVg#5h$OK~ogg=4)yx3kLb&sVth21tQLZ$Z|q`a z9A~6HrDevX>crPci8WZm{6o34j{@SE(Y)mM?EGKBs<&bI% zu!i(bTFw5#=6lpaj2H|21t8`qU!M^KxgT!?eerZk zo~kfW)ZN)kZG7;1UEYwglJ*R|KVSx4nAvQi)TJ#|uAJ1lss?I%Uh>ox)c z&)F#RK3bdJyYUr_*qsJ* zHllby4**#&c-Y&YWg^-Vu8DBwP%|k8+1Lt7Jxy@dC~Pn3>)tJn%H}gNUL97b|62oB z=9-AEYM@gS&)MGrUEOtV!jCdmoUg57YPNf$$n}E{Fxt<&Cve)_HXpA|md$Z@Cr3S1 z3DEHifT{+XgmonRer-%sBZ9-8vV7L2ds!>RmwxoY(!|d{L1>UgLSl6G9Xe_gVkJkP zf$qNr@tu8?eZH~XG0+>iob4VWCI4EvybN&Y`#>N)`^fLwq5968c=`01K0T9^RsG)% zY|r;Vtj?BejpH`^U$;Jg%)ila;hp1wq5R`&6dv!#6%ZF_zV7&sCv(Kc|>Hii> zv1xE>eFv}|0IeN2v$f3#gK&c=xHW|^|4sKYjfJIm_jB3^abD`63->|1Z@(tWSP*0n zZ@iHY^Pgz*hI&Ec0X0Y_91bgIi6gf+TWbuhY{&eDLiF@9Uyow#yw4MmaUyH~w` z;05eK@2ZSIx8AvUiYy%nvnc?(&VHFU2kGw(ep*x`r>d1qma4hXmk50|`BcOWblml) zgPCcQ?>QJ+Se^fJc7DhH#RSSB!M_hqY`L*-{qRP!KG=B~-h#HJA8NDF;>l8e;+ocP@9b^mEzZq9#T-JjsV9frLCMRBzo^uhnMoz4>)-7^;x5Q-m= z$QBv9W1zPe4hATsK`A|9s6PGe`2^jb^5+@beYelTHsaWlZhX0T|26C6yR|#G-ImCp zy>_L5rIa5}y8Hwz+Ft(A) zc5U@0W^+6l9k)n?Lv#?#RqipRK8?4is#PFon&LrU9s<)|nPB^8Nm{F24-mQJvQ04aNkZ4^OVvI^4fjp*5P3o*YZ+iZtU+ zabuX^n3_OFldmshchb)&db!;k>hy|vfH6R~z60$2{>V3ea40Ew(blh1Wo@{H%OvM9 z3)EOH2&tlD!=zNCRyS#*{0uM^P|b=fBmm7x`1kzI13Kb&T-KhXe&n=3^BR_n_?L~gxHt%6l8)P@_U?rWyWHPqHb*Z9{00hxt5KVGS``$ESU53>J9 z7E?dq3660$T!MmvY_028zAocdESfe`sB=VN?(QOx$*$!IX`V%FwfNe3v*L#d9EFeT zg+6DZEfAM~dHEes8vMt(sGmC2&i@PXUZx_sOdVQ6eEOpkQE{P?({qe_4Nxm zy$lj71Rpb?l;*IbH_L<(m-A3K0M9irGU-<;?Bo81XaRc@tpe_72jHP7sgNZYHCku? zwya&}WH4I_NB|J#*zkY?$m?7frvDUkc>N!EDemTFpCH!( zQ{o{212m7KqJgZZ*05|aYzOyl{B!Pt<$n;PXYo3m-Tb`Fo9gP-UijU%@mW}0{NdGH z6oJY?1+ntgH=9o|nM}y;cM(3>`v5G11V-?Mx5`;#ye4Y{3N1>yzL!tG7ht4Ya#wRh zDQecOUS?3-p39B^$mCcfNHM1<0>4uXZJ3p$j%?f1S4bncTbq7$Al;>|gYq1QVlq9Y|WVRdojUbsATR zPJ&Yp$U?Hs^9PN8x{A2jBaYbHlYX{8oeVIKOeAM7UW;zIQ@-22HN${N!aMsBrlUbr zSFQ?&<+<5Y0&u%i5cZ0NMTwncsp!uD>iWm&82~3620#R$a*IHwbK#7mNOMXzGba$b z`=FjGv)K=s`~M_ox<*<%14JFpW+A$EWs?Z*UPl9@6&YmAW-67J;*^(ey(mu!cII9Mb~9? zZcZ$Wz{p`_4jl{pfrF+Zcw%A?eB$pUtc=;sWpx9D0J)!sZ1cg}qrU2>c4=J^Gl~`| zd1d8G&hFLt6~bg#!d*4lUYs9?MQk>vg^?rL-LiHEO8z_k)@qW&PSE0D^^Sn9t)+tZ zo^IwQFG5PL5`>WBsJnFj&)jv*kctv+&@5x9X}~5;(-_RCq?)> z6l+d;*cLXqGYsK2P{W`zvkF=l;({JYn4i*<5{)W;bsln30Uez!6zz(NLFtGICpj)9 z1bvcVXdh=-;JAxT)}8dKlCU*#@-9KGep*lgJ_=AroqthBspT@F6cZpq9N=f(ovAAG z&u_T+0Qx;(yo^l0t{d&i3c@2V{x!$+pw;0LAjmNfCXT{xl4r9cIc1yKpY8B12 zC?GZg_H&Rk&&R3H^xP5dTnDN!*X@OY(#Ai^8$6HC1D^r(0923<0k{SPdhlx_@c+W^ zAYKsY?N0#n1l>CRZ+oBvJ>A!1KaaeVGkrUt%+jPNKmUsPsfGQjy2^qVyu7?G?&Lha zQBa;UI50GHy6_N)ZVW#RlD*n=XHw8iUw5!_J5gFwZ8Kl4AT31yMDCRx)l*o_!4W0e zfN}S(Kuo9SAMa(cmAN$`UhfJb*R!S{40##2ns^T)qND<7?ye|gYfiGQ?`TePI+E@# z4X$#)7t}7GaeW&?7!N{8Ms?SC;nkrbHB2B~=))yjyL_mVFA)_;x8BNej)AZ4m9!_B zUmn8xGm^2^_=vdLGAj|WTE0Beagb#-Fy%21&xsclN!6LO5#_O$llk<;G4S7SfDI>~ zL#xvpTquUBgqzkc3UhFn$gv6qW?!-RrLd5u+HS~x?HC~r*C&e)yehul5U{3#s;02s zdx~SirZ?;@94A+EEhh-@VZ_3_evhr0*I zyX%5>+7i;l^?9uiZx{#9F0IWEsti^fZ=C1EcRKR$QEi!`9@MGD|Y-mwgvi)P~+jHl*F-+=5WY#)-F)O|jlvE75TJ;uA+ApAgL=g)6d=A+8Y-rM?uNVy4s<*Di>J@ zJ4>a(J7CuJori~yx3{~eSGC9F4`p#53Ps9|BAVg=v(tDKV5<+;yR-9`USw2=wRoDg z-|CXUus=gcwQ7i9OiqoEkxv*`}_+;>!;mPP&$qYGS?Bt}n>6xqDwLz@Q%c-GruvH7^ zGZT;Tq3xc0d;m{v%)ATYEnPZEvrQ(he<1Rzi!VufkFUsjSGu~f`o&PN5~ikjE!1H}r;zuVpjbancwxjG|9 zG_v5>+A~EAf=t@x%axw;luDNY1*90GbK+CBVhnQ+tTIM;Ta~xbEZa2(O}Sz#rXjrj zESOc4sFy=ZjfuW-4Ov|%Uc9|i$M zk~a8wrX(N9Y@`C6R0DWx9Q@kE%?LI!85ca9!qlB2K z*kqkrN&WYw%)UfV_M9HSuwKB^Lcd!UMrHi|!ET@MC8f4+lK3y--%vOHdekTBx}4;y z7T4ecv#T?ik3g^E>LZEV*$)^Fb(vH-kG04Q zdE{xWdex?m36J7!ynQ~L$MP>-dITRkObV#aXqWCQz3 z4aoixzzaa9v%H>RLnuoFym> z=O#sYM)E}w&=ICe7^q*?^NU{s_R78|%A6Clh^6w_s?6UDFO1$B{e;nA`?^=xDb_Z7 zn>&Z9_Dd+*-lV4jE@zH;O~0LW6E1=wH$I10`&mQg!N2^{k{c(NEQv12hj!qej73Hb zRjR4kRs}jK4KsGO6H`wrg5XDip#9m@EQ2~}vs@`?H7JyfeY0jLF_sZ@@yj>a_c7M! zlbZ8aN?nSK6CqYGC(L}WpSOX-%;8rR$tQoQV~!O+JhnF8MrdUYn{j?W3%}!W=|+Zs z)<_R&bO=T}wR#n%c2w}Fpq^CY%P|b6)0+nrlL}B3%MZGSFI0YOZ+hKgfah)vO%8>+#8BY4VKtJty6 zYrHg4eRM9#kVBj9o0_t3_5B8teFC`LJ@@ZN&@>YbVZm4kZ+_tSp7p=_?F9ve=5sVv z2a7Te6&7I1IMrnuSY&QaP6fWc^Fj_TZXrpb7+eQ`e8|Hv62i6dE?QR^07D`J=VL9*Hh{_Z~alf^0V@`%=m zP0zEZq|+xnzTff!Ttt8Dsgz;}fGKYh=qm{Ju?GeRfR(bfwLdJw=%Dh;x%stURn0Zm+Khw zgxw1W(}Oa`jh@sty-t#X^$s@Jb~;vRZ8o4U4_$bE%o#+NJzE|qBwb03<2u_lwdF!s$0n97qRi74}XMt8yKvy zbMoz;Kx9<|1%-^@kCajoYMre!kwcZL%9x$RK-I|&V~B;T5%NZ$L4JuAoD9Q#9K|-F zcAeg1hfc-o$T6#x1CCNwh73#7sN)~DQeuRgp5U zS%03kv7P3S+fU714E8-CD7+IG;4k=L!FjqHHT@Wg%-^M#a2>W5ve5fhq5|o~3~%OM zM`u*l%8wY_eoW^HLH~iyR`npWFgpKEra~r`27Iqx-kgWnoGrl&3stJ9z=YSQQA(h| zAM!n*?wEqwxQOYvnlyx(R(FiPiIkmL%gSN=>Q?6N)61sAsu=$d!vj@2LLX8+qj_$9 zegop|K=<8(>4zDZo#V#VRl!4ryW7^wGqRZ}E{;Bu20JQ)FE5Td?Q}|dVsuJ_a{CvV zVSy?ZxmM+0YIC!4;IuHuypMy;EoMHjZMwhmut;G6%}Ve?`wK1a*s8&ox6KS3i6eH& zO?R~9Vyy-9&7|Cy&!m%91Q&$|2M4kK$h0DvwAbF+qc4gl$!Q zTCnHEeCoDNS*5yrwwe3A^PZs1{>W@6&qm<7yc%4~9qDxIo68s=Syv4Tf7X-$maJdO z=?vz(RKFLdZ5vm5Gr-NJ!BDdDL{>!+mr;nVw<@SzmQ?UzJe~q)%?epmr)x zGsjcML$ErU&kC#zY-|GSsQPQg8N7oAlD3kuFL6VW6cHNwv4N*6%vHbhmYzN{wQu(i zANDxAr}4|;ku?9EwwA~tdR!bmY5#mOYG7n2q4jFrRY-eMWG*GlYgJNKa2s=)e_UMb ziA=kM6vd|ix^i_8Q+y`W>uX8jm5?VDXHge^F29nt+U>Ji;;}*z@1Qkc1`->dQ^LeY z37D>k0>ThxXy4(f9&}2G#Fb<-4F@st%c&VRF_f^oW%qQiK;_ z6jN051&0e@HB7n)Vl$>6lCTfd&gFjW#(*PbTT^eA8{L5q2clMv=*0EeL*Zfhg;iA* zSY#i&JZdYWkx7U(sA4A>(te;DEav-ekyAshJVS0;i;HWt3wnF+3_5z^n$E0Gz0hJa zoS5-TLfS6xal-wqt;3D}qld-pPYXgNx@NMmIhVMC_4#FJrJl#u*g^LvQTli8 zG?@V)d34vqbWjZzXd!qqu+))KnG2R5P{zPiu&Q%-l8E)#`2Nb+@X;i3ENM1yEXK}m zxNmUMJgpe!xb_U`4bQ5<7nWPas?6geNwT(LF8=GItriMmtE(}O;z$LzYcOaWj)rqS z+}mH1+cPoQ|0v~V>diWVoPDonWyQF%fyq&e!HIsYeN)?{rE8{)A>7(&OR1%Gn-e08 z?LZatkgp@Xwr?T{U1$!WtC#ng#SoU^uNFV}IJiyinI3ek5-~HXXpc#0ddVsHZft-_ zsY;Y?XYYw+4iEN^np#EeC!sTG#$kG{Q0uVvA5Xj+BFDR)@lmZ!g8K}<7G!#9mvt_A zV`5t{nXQ;>$oJBFzC~;C4|I4}hwTD5tLU-k$mJCy;i5Ass~4vcTN*Y1r)KwRH!g$-j_($mXZG=Q(rv~ja znv+Rr?K+KRC;6~{$m1+twjJNv>W@7-hEY*F3Xm`5Q+d~#ZiE;GviP4y>iB|Ox`4P{ z@zvN&<*7~O+u@ln?*}pTL~vbRT@G4tf;?zrZR@o((fj-R{*O4#o?c^<glaXH!QUM`xokS)%V|3yG_kR@+2^% zUa|ElgR}*s(DI#jY>si2H~o}@g&rqx19vKLby0g>)m-)IZm{Ur2{xm^*tWlCjARaG zH|#kVx@b0e>x^h1jB+`zy5@wxI~88jG;;OeAMq3A;R_-q?%&iHI%z#Fp7vxl?YEnJ zbz-X;bsyiPMl$hxm#{90We z1BDtTOEkEyuWQ2?=I012tH3(G^Za$qP0)4EAgwfIR7~iokeb&hq*k*>?c|_fzi33d zL!?PP^koWS&ZYFoJPsG}*raJB+?AdwTohmX(X3+2E_T52=}o2db$)oXSqAO=&1{{Y z-xuj(+GhDd7HagPDtsU%p#5hm*y2aXm%;!_b6L}!7rZ%N(JA|H$J`&H*kiVmcX-+7 zr8}-LV6g3@J`(cD?M-#qr3|n80k&afmATm!3gS{S!aD(2jK7dqx)YV0QDmpBX)ZNZ zjy4M@_71p{QJj(ODsc*DXyxAce zVL;u~q=CHI$*z*j=&X7<`4MN_KQa19Cur1Nrl6p(upn({Ai&?wG+;J|xR~re8GDa< zHw%+fQE{#o0$V~bi`5IVvL^<5Lsl+C)XWYRUR511n+zFrmr0V%_vz|>m1aPi>=(-z z^-6X!PaiV(pse_G@^{kmLNq-1HOL{fM>6IT0Z+zndBYd)>7Bl0>tdlge`~c>=BiTu zs)WxNC%;CQ&R$QbwfyoKAIi#_*VoL2Ro4yN+QuSoGSsE= zh*p@E+#q{xXEgqN-HA@y(RfGNYdyn1RUVlzD`8EUy>K(BB><#a>y-K2K^!lt*t(d( z9%|PvyCkMV>L0X8CmhXVe#`VQ%iDit$mm`kDZ)I79j;0i8?(j~>DDN#S0dO`_UZ?LY5}>VfOhGMXCXIZ1^phdNhvZZC>Txa;KvE&y-JYignS_ zFr8wCP{&-Ax_mPB#`s^pIi%cL+3$Jk@}jrq$ouSvS+a5&Ey6CF?r0R)|XeAjK5w(snNq>GeZuxw!WJ~ z3XSzjhk*9C^Nppuv1`w?a*ao~o@8~t^CppQ2N2Pm z*8+VM_>T_xk($@U?VH!%FGd+jHWr=u|GgX&)4EUGqRJ#69@C3M|FaeVQUtmk?0qe3 zkrL0HB>aP9t&ZdCb)-1KnmdKPc^-x_{66szEiuYyX6Vg>#m;$f%ez-V>k`oFH^M#4 z1A2%8@mVg>$29C1$d)V6SM~5qyQ7R5LN|dRJv#v-!noIbIBg&N)Te$}2K;a`(v)09 zij9WPoRe;*9l5g8Y^=Gvu{$Y}@hiX7uT8=&FBpW};>A76iOne^$62Q>so=pMkIUUN zgIwMniBrF1XL`y=Q&f2Jm<~#DE2G+1zotz;b8i z)pHiB>w!z1s538K5cMX4Ib&o8?)Hx3?sQf=hJ~_i`=x$CT;He0Ouvq&fU5={RZ%bW zC#}bPla|5KW5=?yJ<5;lug7KOeco^Us-^0Y9IvAi1pDw?bd;Vi`AEFxiI?+xGryd0 zc`V=382KP6Q?7Dh<0}dFy?i|;iCCf`2sUr{VW7S%>((wDr-oM$x8h)Fk{jwgVRa>`K{#%Z8bj8+Aho9(>RS#X!OhY)aq1ynnQRNfn1-}w12Uba=SvtW z^e4K`j-_Fu2PHe4s0EhnyuF*;-I5t=VV^1v%%5NZjpJRN{Z0s?W2*N{W_#E6oqG2e z%vnKHbQIzggw^C0;>`I0bTxuGwyYsYiy-w)G8nUU=iu$xJWt?pE&cUOrSPOS}JZw zxcR(rbELE`c+|M24sjWwbxsH+w-?nReYbf%sFjoc53x+qe=;(*R zQ-!m;UibH2FX7K+e)M|%QiOPpf9`~)amx5K@eS@-^EGZjB?75+62`LkPjys?8%|ez zk>Uk)>J-*Imqd`7CydWj7S0Td&q7E_Pw**R#1n)XToHaX%|7oJrrzYEvhwHfwyNH>ej|@?fF5iB^{N!;SPLB0&i3@KNbP!qBf5jrT)2(4%5?d=vD8N!5sAigjEcpd3+tZuXD>A$E!lAQnCdZmeT8lM;A+-`)fYNb=-7Lc#t6~8ZRWv?b$YpHxjpW7Fs ziQiGvTU&J8{TeDrBz{)AyNIO;H?6dl7hG*$VMm4bRJ$5fW{OrZ32zLLnasOuzSkf8zl=Zd^i`EMnyK zik}*7Rq1}*l9YrXN?xhL$B4Hh-s%zU3;(^+B=d9wx61rIS8Exn7)$3&WMByxj_xTFt26YN`Bf!Q7p0VX`J;Z=hC zEy<$gQQ=Hi!U@lYPvz2EO4l@>imqjee`ZFebU0n_PMZDpyh!sxoaHI*G5DWUbB^uo zD)o)X>D=@MU}uOyaT#TDb8S4^C*;LjUY$1>CQXdeaB+w#LPgB}mC!A?H=0!re%3Vo zr~xt{DT*zLSIPMbPT5jQoXlcOt_)ZOK;t^0q`+gglVzKumI$wH8+C+sLCR=((UI9b zt59?5d*MbQ>PGcC4tj6Aap@u7I=LlyVUhUcH00_$8#1B8r@uG;uHjsmhMmKl^n?fL zx{FZbAz+#C^j8rJf)2ReZ_$L)MI{>H#f3@7>Xw%XqQYOLX9)4lQ-3vwZRA$ZZmVOi z(%vs8%_ikXBF_t5CY-anuk)+0_Q|x&H_XVHkrR%CugkWFydmX`1mCXltNivre72@i9u%8L24Di%II!BLIp@$(Id%7s?l0*v(znUe%Gpq$I=)m93I-~>7Lc| zq}>UHQNjy$iz8X(jdkG0(Vtg-Sd}M9NhoNZ?ed5fT=VT7dFi1W_meg~okiczkLPq2 zX_g(=XA)ZRB1D~x_z4#w%}aQXUILKgMk_*4yfbedXB*|lX&XV+>;9MC7a^OoVT6j3 zXkMc?yi%{Ux~Xh$N*(N^40RZ<^OhVoRWNe6usfQ5V(Zw{`sz$-i7q>=my78=*8XjL zD?4!OJRF}Syd`ATby({e{6p8PnP#hpyT^_~zouXD*fW<7)_(3hQK#+0dR}RvNAxYT z#&CRvjb=}Y*ow+U#o{oG+TCWYy6fGy=bz~L8fD@P+G$Gm&b(tSYzJxmB8sJ5HiGA1 z*GuUw=ifCla*NSN$Iz&~P1JZ=(D5QHC%8ycJx<+8Gh9h{QeqW?LOs}Z)kzN#4dk-? zJ8SM-jrt{iG*7g!V5T=}OP^oII)(C*NhF{Mi}D>Ptrg+f?@F$U?_0IrLr$yf@6H!G z?A<9_3@(-Ud$TQ~j_tYmZPj7e6s&#I-v%%1p>v8sdJl=mKHb~pX)M#$R+^4}Aimx$ zeObOcO_6KyS{hqR^X8@oP2!sq@>*_f?UvzUMRYsy%{kb4=~w7rsB(8`)KtRkd#u&a zvp~iuYqo5b*EdElcC8{K)*UIWT~@f&u~5O%*z3jL;WzIJH`AJr>pYSCbK~tx2ab@8 zI#$wfR>~TMTE6Nx4wDAL!<>>#-uM0Bj?bxNJ1^m*Zg(7m?SVC5pt3xMac1Z7Y$;ntN5Li@&e*sqK^O_L( z$GXp}%(e$(<%iO%{2^UG!Iol+xoLGofkD&N5pWq2VF!JM!yR zTK&Q17SXWF;y)q2Jm)LQ&fpQS?*)j6z`#>tivZ*#v;M43_tt%0DD|Vs%*B#d;7~bL z1LkgbP2jC-N%*rm5krhxqHa7R)ZX!Dy9HhP27`9LiT zo#IhSYc)-)%a>qiog_+$>!Cy+et>*fYR~4ahwR&Ms#+3=u^7lQPBc_i=dUU@tE})h ztrIHo%@?ueLcH4YzqDuXU>!Swo+gXzg$34t@%-Znj%jn7Tk$0Q?6lWR*MjK2(?k$b znSE!w!8=lePa!@@=`*8!uRL09(NmojL_()5CslKAZgeA+5V*&K1l9MwtDqW~~jXQy&W3rPV@Y0!?uX@-8cT*B?mKTFm{s7z(G`7{By;3{@$ z$PSgn2QcQOjkI?@?C#gmHmBE43B|9I3vKk5huroT+uXJu4rCl-Cll`JwLEjSvdYi& zcL?35EfvJts#XM>cfCjqQ>KPp)!{W?D+k$^S(gWIEr31>>nF*!;;0Eofmp96#KUz} z?j<#64e@7jtl>?9PFKkhRwur*f88Sf?)1w??04#%N_Ecm^pC%l&+}2?OArph@`FO)+&fhU-bQ6(56YotzBz8zr|gC;XA`W z3!tYb=ESYVR@U~9!NJW?iWleMQSIyF?Wr-rU6~ZuhFGnIlC}gLvHw)V*obS`mkZ`a zR$oyd`Ef4Wg|rPBLF2$SBA)nPyS;x|7|`U~P*80yU>JF}dASSQof=fq>`FQe?1bz% zMg+i)vCdwg=98L%o{f@O8E_msnfQBD%kNRdg+-c)ot8gTlWkh+s^K4elpSE>=c)PR z#)%PcVZyXRTL~yVI8=Qdu54KJ<|ugkyE^p(&@8MWeGm`;-u!|L>_3=`+zqA&Sik*n zUib}+oeToGy!>D9_Wf_}lK)2m>S^n{i=zxJ8Z;}xz|lx0L%6sbRN)!FSNNQWRA$-z z(xC!u-uQc4X9w=}3sK!B^|)>ji2udG^8ya_&nbaG0>CDl|6-^9Lel%xnS;ZB{g;RT zEjRK<1Y-_%;DEww0Db&j!i7#?3$}FF#7CgV)$hO8+}il3(%?=656B+&&;8r~*FV1C zXAtP||2)OvzjBv { + - + + diff --git a/web/components/ErrorBoundary.tsx b/web/components/ErrorBoundary.tsx new file mode 100644 index 0000000..dac8b08 --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,92 @@ +import React, { Component, ReactNode } from 'react'; +import { useTheme } from '../contexts/ThemeContext'; +import { THEMES } from '../constants'; +import { Button } from './ui/Button'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error | null, resetErrorBoundary: () => void }) => { + const { style } = useTheme(); + const isNeo = style === THEMES.NEOBRUTALISM; + + const handleReload = () => { + window.location.reload(); + }; + + const handleHome = () => { + window.location.href = '/'; + }; + + return ( +
+
+
+
+ +
+
+ +

+ Whoops! +

+ +

+ {error?.message || "Something went wrong while displaying this page. Try reloading or going back to home."} +

+ +
+ + +
+
+
+ ); +}; + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( + this.setState({ hasError: false, error: null })} + /> + ); + } + + return this.props.children; + } +}