alternative tangled frontend (extremely wip)

feat: better layout

serenity 51d5617c 1f0b62d1

+103 -52
+53
src/components/Profile/ProfileOverview.tsx
··· 1 + import { Loading } from "@/components/Icons/Loading"; 2 + import { Avatar } from "@/components/Profile/Avatar"; 3 + import { useAvatarQuery } from "@/lib/queries/get-avatar"; 4 + import { useProfileQuery } from "@/lib/queries/get-profile"; 5 + import { useMiniDoc } from "@/lib/queries/resolve-minidoc"; 6 + 7 + export const ProfileOverview = ({ identifier }: { identifier: string }) => { 8 + const { 9 + isLoading: isMiniDocLoading, 10 + error: miniDocQueryErr, 11 + data: miniDocQueryData, 12 + } = useMiniDoc(identifier); 13 + const { 14 + isLoading: isAvatarLoading, 15 + error: avatarQueryErr, 16 + data: avatarQueryData, 17 + } = useAvatarQuery({ 18 + did: miniDocQueryData?.did ?? null, 19 + repoUrl: miniDocQueryData ? new URL(miniDocQueryData.pds) : null, 20 + }); 21 + const { 22 + isLoading: isProfileLoading, 23 + error: profileQueryErr, 24 + data: profileQueryData, 25 + } = useProfileQuery({ 26 + did: miniDocQueryData?.did ?? null, 27 + repoUrl: miniDocQueryData ? new URL(miniDocQueryData.pds) : null, 28 + }); 29 + 30 + const isLoading = 31 + isMiniDocLoading || 32 + !miniDocQueryData || 33 + isAvatarLoading || 34 + !avatarQueryData || 35 + isProfileLoading || 36 + !profileQueryData; 37 + const err = miniDocQueryErr ?? avatarQueryErr ?? profileQueryErr; 38 + 39 + if (isLoading) return <Loading />; 40 + if (err) return <p>{err.message}</p>; 41 + 42 + const avatarUri = avatarQueryData; 43 + 44 + return ( 45 + <div className="bg-surface0 w-fit"> 46 + {/* <p>{JSON.stringify(miniDocQueryData)}</p> */} 47 + <Avatar 48 + uri={avatarUri} 49 + className="outline-overlay0 h-48 rounded-full outline" 50 + /> 51 + </div> 52 + ); 53 + };
+50 -52
src/routes/_layout/$identifier/index.tsx
··· 2 2 import { LucideBookOpen } from "lucide-react"; 3 3 import type { ReactNode } from "react"; 4 4 import { UnderlineIconRouterLink } from "@/components/Animated/UnderlineIconRouterLink"; 5 - import { Loading } from "@/components/Icons/Loading"; 6 - import { Avatar } from "@/components/Profile/Avatar"; 7 - import { useAvatarQuery } from "@/lib/queries/get-avatar"; 8 - import { useProfileQuery } from "@/lib/queries/get-profile"; 9 - import { useMiniDoc } from "@/lib/queries/resolve-minidoc"; 5 + import { z } from "zod/v4"; 6 + import { ProfileOverview } from "@/components/Profile/ProfileOverview"; 7 + 8 + const paramsSchema = z.object({ 9 + tab: z.string().optional(), 10 + }); 10 11 11 12 export const Route = createFileRoute("/_layout/$identifier/")({ 12 13 component: RouteComponent, 14 + validateSearch: paramsSchema, 13 15 }); 14 16 15 17 function RouteComponent() { 16 18 const { identifier } = Route.useParams(); 17 - const { 18 - isLoading: isMiniDocLoading, 19 - error: miniDocQueryErr, 20 - data: miniDocQueryData, 21 - } = useMiniDoc(identifier); 22 - const { 23 - isLoading: isAvatarLoading, 24 - error: avatarQueryErr, 25 - data: avatarQueryData, 26 - } = useAvatarQuery({ 27 - did: miniDocQueryData?.did ?? null, 28 - repoUrl: miniDocQueryData ? new URL(miniDocQueryData.pds) : null, 29 - }); 30 - const { 31 - isLoading: isProfileLoading, 32 - error: profileQueryErr, 33 - data: profileQueryData, 34 - } = useProfileQuery({ 35 - did: miniDocQueryData?.did ?? null, 36 - repoUrl: miniDocQueryData ? new URL(miniDocQueryData.pds) : null, 37 - }); 19 + const { tab } = Route.useSearch(); 38 20 39 - const isLoading = 40 - isMiniDocLoading || 41 - !miniDocQueryData || 42 - isAvatarLoading || 43 - !avatarQueryData || 44 - isProfileLoading || 45 - !profileQueryData; 46 - const err = miniDocQueryErr ?? avatarQueryErr ?? profileQueryErr; 21 + const currTab = tab ?? "overview"; 47 22 48 - if (isLoading) return <Loading />; 49 - if (err) return <p>{err.message}</p>; 50 - 51 - const avatarUri = avatarQueryData; 52 - 53 - const tabs: Array<{ to: string; icon: ReactNode; label: string }> = [ 23 + const tabs: Array<{ 24 + to: string; 25 + icon: ReactNode; 26 + label: string; 27 + isCurrent: boolean; 28 + }> = [ 54 29 { 55 30 to: `/${identifier}`, 56 31 icon: <LucideBookOpen height={18} width={18} />, 57 32 label: "Overview", 33 + isCurrent: currTab === "overview", 34 + }, 35 + { 36 + to: `/${identifier}`, 37 + icon: <LucideBookOpen height={18} width={18} />, 38 + label: "Overview", 39 + isCurrent: currTab === "strings", 40 + }, 41 + { 42 + to: `/${identifier}`, 43 + icon: <LucideBookOpen height={18} width={18} />, 44 + label: "Overview", 45 + isCurrent: currTab === "strings", 46 + }, 47 + { 48 + to: `/${identifier}`, 49 + icon: <LucideBookOpen height={18} width={18} />, 50 + label: "Overview", 51 + isCurrent: currTab === "strings", 58 52 }, 59 53 ]; 60 54 55 + let tabComponent: ReactNode | undefined; 56 + 57 + switch (currTab) { 58 + case "overview": 59 + tabComponent = <ProfileOverview identifier={identifier} />; 60 + break; 61 + default: 62 + tabComponent = <></>; 63 + } 64 + 61 65 return ( 62 66 <div className="flex flex-col items-center"> 63 - <div className="bg-base flex w-screen pb-1 pl-4"> 64 - {tabs.map(({ to, icon, label }) => ( 67 + <div className="bg-base flex w-screen pl-4 gap-2"> 68 + {tabs.map(({ to, icon, label, isCurrent }) => ( 65 69 <UnderlineIconRouterLink 66 70 to={to} 67 71 icon={icon} 68 72 label={label} 69 73 underlineClassName="bg-text" 70 - labelClassName="text-text" 74 + labelClassName={`text-text ${isCurrent ? "font-semibold" : ""}`} 71 75 iconClassName="text-text" 72 76 iconVariants={{}} 73 - className="hover:bg-surface1 p-2 transition-all" 77 + className={`hover:bg-surface1 rounded-t-lg p-2 px-4 transition-all ${isCurrent ? "bg-surface0" : ""}`} 74 78 /> 75 79 ))} 76 80 </div> 77 - <div className="border-overlay0 border-t w-full bg-surface0 min-h-screen"> 78 - <div className="bg-surface0 w-fit"> 79 - <p>{JSON.stringify(miniDocQueryData)}</p> 80 - <Avatar 81 - uri={avatarUri} 82 - className="outline-overlay0 h-48 rounded-full outline" 83 - /> 84 - </div> 81 + <div className="bg-surface0 min-h-screen w-full"> 82 + {tabComponent} 85 83 </div> 86 84 </div> 87 85 );