feat: profiles page

This commit is contained in:
DIYgod 2024-07-09 23:22:54 +08:00
parent 7a6bc2fdbf
commit ddb1cd9769
No known key found for this signature in database
6 changed files with 122 additions and 68 deletions

View File

@ -21,6 +21,7 @@ export function FeedIcon({
return (
<Image
src={image}
loading="lazy"
className={cn("mr-2 shrink-0 rounded-sm", className)}
style={{
width: size,

View File

@ -551,7 +551,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
view: number;
feedId: string;
category: string | null;
isPrivate: boolean | null;
isPrivate: boolean;
} | undefined;
};
};
@ -882,6 +882,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
$get: {
input: {
query: {
userId?: string | undefined;
view?: string | undefined;
};
};
@ -909,7 +910,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
ownerUserId: string | null;
};
category: string | null;
isPrivate: boolean | null;
isPrivate: boolean;
}[];
};
outputFormat: "json";
@ -921,7 +922,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
view: number;
url: string;
category?: string | null | undefined;
isPrivate?: boolean | null | undefined;
isPrivate?: boolean | undefined;
};
};
output: {
@ -949,7 +950,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
view: number;
feedId: string;
category?: string | null | undefined;
isPrivate?: boolean | null | undefined;
isPrivate?: boolean | undefined;
};
};
output: {

View File

@ -69,6 +69,7 @@ export function Component() {
{feed.data.feed.title}
{" "}
|
{" "}
{APP_NAME}
</title>
</Helmet>
@ -98,8 +99,6 @@ export function Component() {
<a className="mb-8" href={`${DEEPLINK_SCHEME}add?id=${id}`}>
<StyledButton>
<FollowIcon className="mr-1 size-3" />
follow on
{" "}
{APP_NAME}
</StyledButton>
</a>

View File

@ -0,0 +1,115 @@
import { FeedIcon } from "@renderer/components/feed-icon"
import { FollowIcon } from "@renderer/components/icons/follow"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@renderer/components/ui/avatar"
import { StyledButton } from "@renderer/components/ui/button"
import { useAuthQuery } from "@renderer/hooks/common"
import { apiClient } from "@renderer/lib/api-fetch"
import { APP_NAME } from "@renderer/lib/constants"
import { defineQuery } from "@renderer/lib/defineQuery"
import { capitalizeFirstLetter } from "@renderer/lib/utils"
import { DEEPLINK_SCHEME } from "@shared/constants"
import { Helmet } from "react-helmet-async"
import { useParams } from "react-router-dom"
import { parse } from "tldts"
export function Component() {
const { id } = useParams()
const user = useAuthQuery(defineQuery(["profiles", id], async () => {
const res = await apiClient.profiles.$get({
query: { id: id! },
})
return res.data
}), {
enabled: !!id,
})
const subscriptions = useAuthQuery(defineQuery(["subscriptions", user.data?.id], async () => {
const res = await apiClient.subscriptions.$get({
query: { userId: user.data?.id },
})
const groupFolder = {} as Record<string, typeof res.data>
for (const subscription of (res.data || [])) {
if (!subscription.category && subscription.feeds) {
const { siteUrl } = subscription.feeds
if (!siteUrl) continue
const parsed = parse(siteUrl)
parsed.domain &&
(subscription.category = capitalizeFirstLetter(parsed.domain))
}
if (subscription.category) {
if (!groupFolder[subscription.category]) {
groupFolder[subscription.category] = []
}
groupFolder[subscription.category].push(subscription)
}
}
return groupFolder
}), {
enabled: !!user.data?.id,
})
return (
<>
{user.data && (
<div className="mx-auto mt-12 flex max-w-5xl flex-col items-center justify-center p-4 lg:p-0">
<Helmet>
<title>
{user.data.name}
{" "}
|
{" "}
{APP_NAME}
</title>
</Helmet>
<Avatar className="aspect-square size-16">
<AvatarImage src={user.data.image || undefined} />
<AvatarFallback>{user.data.name?.slice(0, 2)}</AvatarFallback>
</Avatar>
<div className="flex flex-col items-center">
<div className="mb-2 mt-4 flex items-center text-2xl font-bold">
<h1>{user.data.name}</h1>
</div>
<div className="mb-8 text-sm text-zinc-500">
{user.data.handle}
</div>
</div>
<div className="mb-12 w-96 space-y-10">
{Object.keys(subscriptions.data || {}).map((category) => (
<div key={category}>
<div className="mb-4 flex items-center text-2xl font-bold">
<h3>{category}</h3>
</div>
<div>
{subscriptions.data?.[category].map((subscription) => (
<div key={subscription.feedId} className="group relative border-b py-5">
<a className="flex flex-1" href={subscription.feeds.siteUrl!} target="_blank">
<FeedIcon feed={subscription.feeds} size={22} className="mr-3" />
<div className="flex-1">
<div className="truncate font-medium leading-none">{subscription.feeds?.title}</div>
<div className="mt-1 line-clamp-1 text-xs text-zinc-500">{subscription.feeds?.description}</div>
</div>
</a>
<a className="absolute left-full top-5 opacity-0 transition-opacity group-hover:opacity-100" href={`${DEEPLINK_SCHEME}add?id=${subscription.feeds?.id}`}>
<StyledButton>
<FollowIcon className="mr-1 size-3" />
{APP_NAME}
</StyledButton>
</a>
</div>
))}
</div>
</div>
))}
</div>
</div>
)}
</>
)
}

View File

@ -1,62 +0,0 @@
import { useUser } from "@renderer/atoms/user"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@renderer/components/ui/avatar"
import { StyledButton } from "@renderer/components/ui/button"
import { useSignOut } from "@renderer/hooks/biz/useSignOut"
import { views } from "@renderer/lib/constants"
import { cn } from "@renderer/lib/utils"
import { FeedList } from "@renderer/modules/feed-column/list"
export function Component() {
const user = useUser()
const signOut = useSignOut()
return (
<div className="flex w-full gap-10 px-10 py-16">
<div className="flex min-w-40 flex-1 flex-col items-center gap-8 pt-20">
<Avatar className="aspect-square size-32">
<AvatarImage src={user?.image || undefined} />
<AvatarFallback>{user?.name?.slice(0, 2)}</AvatarFallback>
</Avatar>
<div className="text-4xl font-bold">{user?.name}</div>
<div>
<StyledButton
variant="outline"
onClick={() => {
signOut()
}}
>
Sign out
</StyledButton>
</div>
</div>
<div className="flex flex-[2] flex-col gap-8">
<div className="flex h-full flex-col gap-4">
<div className="text-2xl font-bold">Subscriptions</div>
<div className="overflow-y-auto">
{views.map((view, index) => (
<div key={view.name}>
<div className="mb-1 flex items-center gap-2 font-semibold">
<span className={cn("flex", view.className)}>
{view.icon}
</span>
<span>{view.name}</span>
</div>
<FeedList
key={view.name}
className="mb-4"
view={index}
hideTitle
/>
</div>
))}
</div>
</div>
</div>
</div>
)
}