alternative tangled frontend (extremely wip)

feat: trending feed

serenity e8ce5e7b 70a32184

+176 -38
+17 -17
.cta.json
··· 1 1 { 2 - "projectName": "strand", 3 - "mode": "file-router", 4 - "typescript": true, 5 - "tailwind": true, 6 - "packageManager": "pnpm", 7 - "addOnOptions": {}, 8 - "git": true, 9 - "version": 1, 10 - "framework": "react-cra", 11 - "chosenAddOns": [ 12 - "eslint", 13 - "nitro", 14 - "start", 15 - "tanstack-query", 16 - "compiler" 17 - ] 18 - } 2 + "projectName": "strand", 3 + "mode": "file-router", 4 + "typescript": true, 5 + "tailwind": true, 6 + "packageManager": "pnpm", 7 + "addOnOptions": {}, 8 + "git": true, 9 + "version": 1, 10 + "framework": "react-cra", 11 + "chosenAddOns": [ 12 + "eslint", 13 + "nitro", 14 + "start", 15 + "tanstack-query", 16 + "compiler" 17 + ] 18 + }
+1 -1
src/components/Animated/UnderlineIconRouterLink.tsx
··· 43 43 <motion.div initial="initial" whileHover="hover"> 44 44 <Link 45 45 to={to} 46 - className={`flex cursor-pointer items-center gap-1 pl-2 ${className}`} 46 + className={`flex cursor-pointer items-center gap-1 ${className}`} 47 47 target={target} 48 48 > 49 49 {position === "left" && iconElement}
+96 -13
src/components/Homepage/TrendingFeed.tsx
··· 1 1 import { UnderlineLink } from "@/components/Animated/UnderlinedLink"; 2 + import { UnderlineIconRouterLink } from "@/components/Animated/UnderlineIconRouterLink"; 3 + import { Loading } from "@/components/Icons/Loading"; 4 + import { LucideBookMarked } from "@/components/Icons/LucideBookMarked"; 5 + import { LucideStar } from "@/components/Icons/LucideStar"; 6 + import { useTrendingQuery } from "@/lib/queries/get-trending-from-stitch"; 7 + import { AppBskyFeedPost } from "@/lib/types/lexicons/app/bsky/feed/post"; 2 8 import { Link } from "@tanstack/react-router"; 3 9 4 10 export const TrendingFeed = () => { 11 + const { 12 + isLoading: isTrendingLoading, 13 + error: trendingQueryError, 14 + data: trendingPosts, 15 + } = useTrendingQuery(); 16 + 5 17 return ( 6 - <div> 7 - <p> 8 - Powered by{" "} 9 - <UnderlineLink 10 - href="https://catsky.social/profile/stitch.selfhosted.social" 11 - target="_blank" 12 - underlineColor="bg-accent" 13 - className="text-accent" 14 - > 15 - stitch.selfhosted.social 16 - </UnderlineLink> 17 - . 18 - </p> 18 + <div className="flex flex-col gap-4 p-4"> 19 + {isTrendingLoading ? ( 20 + <> 21 + <p> 22 + Powered by{" "} 23 + <UnderlineLink 24 + href="https://catsky.social/profile/stitch.selfhosted.social" 25 + target="_blank" 26 + underlineColor="bg-accent" 27 + className="text-accent" 28 + > 29 + stitch.selfhosted.social 30 + </UnderlineLink> 31 + . 32 + </p> 33 + <Loading /> 34 + </> 35 + ) : trendingQueryError ? ( 36 + <p>{trendingQueryError.message}</p> 37 + ) : ( 38 + <> 39 + <div> 40 + <h1 className="text-xl font-semibold">Trending</h1> 41 + <p> 42 + Powered by{" "} 43 + <UnderlineLink 44 + href="https://catsky.social/profile/stitch.selfhosted.social" 45 + target="_blank" 46 + underlineColor="bg-accent" 47 + className="text-accent" 48 + > 49 + stitch.selfhosted.social 50 + </UnderlineLink> 51 + . 52 + </p> 53 + </div> 54 + <TrendingRepoInfo 55 + posts={trendingPosts?.map((post) => post.value)} 56 + /> 57 + </> 58 + )} 19 59 </div> 20 60 ); 21 61 }; 62 + 63 + const TrendingRepoInfo = ({ 64 + posts, 65 + }: { 66 + posts: AppBskyFeedPost[] | undefined; 67 + }) => { 68 + if (!posts) return <p>:(</p>; 69 + 70 + return posts.map((post) => { 71 + const paragraphs = post.text.split("\n"); 72 + 73 + const repoInfo = paragraphs[0].split(" "); 74 + const starInfo = paragraphs[1].split(" "); 75 + 76 + const starCount = starInfo[starInfo.length - 1]; 77 + const repoName = repoInfo[0]; 78 + const repoDesc = repoInfo.slice(1, repoInfo.length).join(" "); 79 + const repoUrlRelative = `/${repoInfo[0]}`; 80 + 81 + // because sometimes stitch doesn't post the star counts 82 + if (isNaN(parseInt(starCount))) return undefined; 83 + 84 + return ( 85 + <div className="bg-surface0 border-overlay0 flex items-start justify-between rounded-sm border p-4"> 86 + <div> 87 + <UnderlineIconRouterLink 88 + to={repoUrlRelative} 89 + icon={LucideBookMarked({})} 90 + label={repoName} 91 + iconClassName="text-text" 92 + labelClassName="text-text" 93 + underlineClassName="bg-text" 94 + /> 95 + <p className="text-subtext max-w-164">{repoDesc}</p> 96 + </div> 97 + <button className="flex items-center gap-1"> 98 + <LucideStar /> 99 + <p>{starCount}</p> 100 + </button> 101 + </div> 102 + ); 103 + }); 104 + };
+25
src/components/Icons/LucideBookMarked.tsx
··· 1 + import { SVGProps } from "react"; 2 + 3 + export function LucideBookMarked(props: SVGProps<SVGSVGElement>) { 4 + return ( 5 + <svg 6 + xmlns="http://www.w3.org/2000/svg" 7 + width="1em" 8 + height="1em" 9 + viewBox="0 0 24 24" 10 + {...props} 11 + > 12 + {/* Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE */} 13 + <g 14 + fill="none" 15 + stroke="currentColor" 16 + strokeLinecap="round" 17 + strokeLinejoin="round" 18 + strokeWidth="2" 19 + > 20 + <path d="M10 2v8l3-3l3 3V2" /> 21 + <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" /> 22 + </g> 23 + </svg> 24 + ); 25 + }
+23
src/components/Icons/LucideStar.tsx
··· 1 + import { SVGProps } from "react"; 2 + 3 + export function LucideStar(props: SVGProps<SVGSVGElement>) { 4 + return ( 5 + <svg 6 + xmlns="http://www.w3.org/2000/svg" 7 + width="1em" 8 + height="1em" 9 + viewBox="0 0 24 24" 10 + {...props} 11 + > 12 + {/* Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE */} 13 + <path 14 + fill="none" 15 + stroke="currentColor" 16 + strokeLinecap="round" 17 + strokeLinejoin="round" 18 + strokeWidth="2" 19 + d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z" 20 + /> 21 + </svg> 22 + ); 23 + }
+1 -1
src/components/Nav/NavBarAuthed.tsx
··· 29 29 iconClassName="text-text" 30 30 labelClassName="text-text" 31 31 underlineClassName="bg-text" 32 - className="text-lg font-semibold" 32 + className="text-lg font-semibold pl-2" 33 33 /> 34 34 </div> 35 35 <div className="flex max-h-12 items-center gap-1">
+1 -1
src/components/Nav/NavBarUnauthed.tsx
··· 13 13 iconClassName="text-text" 14 14 labelClassName="text-text" 15 15 underlineClassName="bg-text" 16 - className="text-lg font-semibold" 16 + className="text-lg font-semibold pl-2" 17 17 /> 18 18 </div> 19 19 <div className="flex items-center gap-1">
+1 -1
src/lib/types/lexicons/app/bsky/feed/post.ts
··· 103 103 }) 104 104 .describe("Record containing a Bluesky post."); 105 105 106 - export type Post = z.infer<typeof appBskyFeedPostSchema>; 106 + export type AppBskyFeedPost = z.infer<typeof appBskyFeedPostSchema>;