tangled
alpha
login
or
join now
isuggest.selfce.st
/
strand
3
fork
atom
alternative tangled frontend (extremely wip)
3
fork
atom
overview
issues
pulls
pipelines
feat: better layout
serenity
2 weeks ago
51d5617c
1f0b62d1
+103
-52
2 changed files
expand all
collapse all
unified
split
src
components
Profile
ProfileOverview.tsx
routes
_layout
$identifier
index.tsx
+53
src/components/Profile/ProfileOverview.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
import { LucideBookOpen } from "lucide-react";
3
import type { ReactNode } from "react";
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";
0
10
11
export const Route = createFileRoute("/_layout/$identifier/")({
12
component: RouteComponent,
0
13
});
14
15
function RouteComponent() {
16
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
-
});
38
39
-
const isLoading =
40
-
isMiniDocLoading ||
41
-
!miniDocQueryData ||
42
-
isAvatarLoading ||
43
-
!avatarQueryData ||
44
-
isProfileLoading ||
45
-
!profileQueryData;
46
-
const err = miniDocQueryErr ?? avatarQueryErr ?? profileQueryErr;
47
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 }> = [
54
{
55
to: `/${identifier}`,
56
icon: <LucideBookOpen height={18} width={18} />,
57
label: "Overview",
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
58
},
59
];
60
0
0
0
0
0
0
0
0
0
0
61
return (
62
<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 }) => (
65
<UnderlineIconRouterLink
66
to={to}
67
icon={icon}
68
label={label}
69
underlineClassName="bg-text"
70
-
labelClassName="text-text"
71
iconClassName="text-text"
72
iconVariants={{}}
73
-
className="hover:bg-surface1 p-2 transition-all"
74
/>
75
))}
76
</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>
85
</div>
86
</div>
87
);
···
2
import { LucideBookOpen } from "lucide-react";
3
import type { ReactNode } from "react";
4
import { UnderlineIconRouterLink } from "@/components/Animated/UnderlineIconRouterLink";
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
+
});
11
12
export const Route = createFileRoute("/_layout/$identifier/")({
13
component: RouteComponent,
14
+
validateSearch: paramsSchema,
15
});
16
17
function RouteComponent() {
18
const { identifier } = Route.useParams();
19
+
const { tab } = Route.useSearch();
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
20
21
+
const currTab = tab ?? "overview";
0
0
0
0
0
0
0
22
23
+
const tabs: Array<{
24
+
to: string;
25
+
icon: ReactNode;
26
+
label: string;
27
+
isCurrent: boolean;
28
+
}> = [
29
{
30
to: `/${identifier}`,
31
icon: <LucideBookOpen height={18} width={18} />,
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",
52
},
53
];
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
+
65
return (
66
<div className="flex flex-col items-center">
67
+
<div className="bg-base flex w-screen pl-4 gap-2">
68
+
{tabs.map(({ to, icon, label, isCurrent }) => (
69
<UnderlineIconRouterLink
70
to={to}
71
icon={icon}
72
label={label}
73
underlineClassName="bg-text"
74
+
labelClassName={`text-text ${isCurrent ? "font-semibold" : ""}`}
75
iconClassName="text-text"
76
iconVariants={{}}
77
+
className={`hover:bg-surface1 rounded-t-lg p-2 px-4 transition-all ${isCurrent ? "bg-surface0" : ""}`}
78
/>
79
))}
80
</div>
81
+
<div className="bg-surface0 min-h-screen w-full">
82
+
{tabComponent}
0
0
0
0
0
0
83
</div>
84
</div>
85
);