alternative tangled frontend (extremely wip)
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: user loaders

serenity 7c5255a1 dacd2ce5

+68 -18
+1 -3
src/components/Homepage/TrendingFeed.tsx
··· 63 63 </UnderlineLink> 64 64 </p> 65 65 </div> 66 - <TrendingRepoInfo 67 - posts={trendingPosts?.map((post) => post.value)} 68 - /> 66 + <TrendingRepoInfo posts={trendingPosts} /> 69 67 </> 70 68 )} 71 69 </div>
+10 -8
src/components/Nav/NavBarAuthed.tsx
··· 41 41 <div className="mr-3 flex max-h-12 items-center gap-1"> 42 42 <div> 43 43 {isAvatarLoading ? ( 44 - <Loading /> 44 + <div className="flex min-w-10 h-12 items-center"> 45 + <Loading /> 46 + </div> 45 47 ) : avatarQueryError ? ( 46 48 <p>{avatarQueryError.message}</p> 47 49 ) : ( ··· 54 56 }; 55 57 56 58 const AvatarWithDropdown = ({ uri }: { uri: string | undefined }) => { 57 - const [showDropdown, setShowDropdown] = useState(false); 59 + // const [showDropdown, setShowDropdown] = useState(false); 58 60 const { handle } = useOAuth(); 59 61 60 - const dropdownRef = useRef<HTMLDivElement>(null); 62 + // const dropdownRef = useRef<HTMLDivElement>(null); 61 63 62 - useModalEscapeEffect({ setShowModal: setShowDropdown }); 63 - useModalMousedownEffect({ 64 - setShowModal: setShowDropdown, 65 - modalRef: dropdownRef, 66 - }); 64 + // useModalEscapeEffect({ setShowModal: setShowDropdown }); 65 + // useModalMousedownEffect({ 66 + // setShowModal: setShowDropdown, 67 + // modalRef: dropdownRef, 68 + // }); 67 69 68 70 const AvatarButton = <Avatar uri={uri} />; 69 71
+35 -2
src/components/Profile/Avatar.tsx
··· 1 + import { useState, useEffect } from "react"; 2 + import { AnimatePresence, motion } from "motion/react"; 3 + 1 4 export const Avatar = ({ 2 5 uri, 3 - className = "outline-accent h-10 rounded-full outline", 6 + className = "outline-accent h-10 rounded-full min-w-10 outline", 4 7 }: { 5 8 uri: string | undefined; 6 9 className?: string; 7 10 }) => { 8 - return <img src={uri} className={className} />; 11 + const [loaded, setLoaded] = useState(false); 12 + 13 + useEffect(() => { 14 + setLoaded(false); 15 + }, [uri]); 16 + 17 + return ( 18 + <div className={`relative overflow-hidden ${className}`}> 19 + <AnimatePresence> 20 + {(!loaded || !uri) && ( 21 + <motion.div 22 + key="skeleton" 23 + className="bg-overlay0 absolute inset-0 w-24 animate-pulse" 24 + exit={{ opacity: 0 }} 25 + transition={{ duration: 0.2 }} 26 + /> 27 + )} 28 + </AnimatePresence> 29 + 30 + <motion.img 31 + key={uri} 32 + src={uri} 33 + onLoad={() => setLoaded(true)} 34 + className="size-full object-cover" 35 + initial={{ opacity: 0 }} 36 + animate={{ opacity: loaded ? 1 : 0 }} 37 + transition={{ duration: 0.2 }} 38 + alt={`Your profile picture found at ${uri}`} 39 + /> 40 + </div> 41 + ); 9 42 };
+16 -2
src/routes/_layout/index.tsx
··· 5 5 import { Loading } from "@/components/Icons/Loading"; 6 6 import { useOAuth } from "@/lib/oauth"; 7 7 import { useAvatarQuery } from "@/lib/queries/get-avatar"; 8 + import { stitchTrendingQueryOptions } from "@/lib/queries/get-trending-from-stitch"; 9 + import { Avatar } from "@/components/Profile/Avatar"; 8 10 9 - export const Route = createFileRoute("/_layout/")({ component: App }); 11 + export const Route = createFileRoute("/_layout/")({ 12 + //@ts-expect-error fucky wucky client typies 13 + loader: ({ context: { queryClient } }) => { 14 + return queryClient.ensureQueryData(stitchTrendingQueryOptions); 15 + }, 16 + component: App, 17 + }); 10 18 11 19 function App() { 12 20 return ( ··· 33 41 return ( 34 42 <div className="border-overlay0 flex flex-col items-start gap-4"> 35 43 <div className="flex items-center gap-4 pl-2"> 36 - <img src={avatarSrc} className="h-20 w-20 rounded-full" /> 44 + {!avatarSrc ? ( 45 + <div className="flex h-24 items-center w-24 justify-center"> 46 + <Loading /> 47 + </div> 48 + ) : ( 49 + <Avatar uri={avatarSrc} className="h-24 rounded-full min-w-24" /> 50 + )} 37 51 <div> 38 52 <h2 className="text-xl font-semibold"> 39 53 Hello @{handle}!