Post card improvement

This commit is contained in:
Max 2025-02-09 11:49:37 +03:00
parent d1474bce35
commit 1659e007e4
12 changed files with 180 additions and 90 deletions

View File

@ -17,6 +17,7 @@ export type Post = {
post_id: string; post_id: string;
title: string; title: string;
user_id: string; user_id: string;
content: string;
}; };
export type SelectedPostsResponse = { export type SelectedPostsResponse = {

View File

@ -8,8 +8,8 @@ export default function LoginButton() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Link to={"/login"}> <Link to={"/login"} className="size-full">
<Flex className="justify-between gap-2"> <Flex className="items-center justify-start h-full gap-2">
<Icon> <Icon>
<EnterIcon /> <EnterIcon />
</Icon> </Icon>

View File

@ -36,7 +36,7 @@ export default function LogoutButton() {
return ( return (
<Flex <Flex
className="justify-between gap-2" className="items-center justify-start gap-2 cursor-pointer size-full"
onClick={() => logoutMutation.mutate()} onClick={() => logoutMutation.mutate()}
> >
<Icon> <Icon>

View File

@ -50,7 +50,7 @@ export default function UserCard(props: TUserCard) {
}; };
const getUsername = (): string => { const getUsername = (): string => {
if (!user || props.username) return props.username || "Nothing"; if (!user || props.username) return props.username || "";
return user.username; return user.username;
}; };
@ -76,7 +76,7 @@ export default function UserCard(props: TUserCard) {
)} )}
<Text size={"1"} color={"gray"}> <Text size={"1"} color={"gray"}>
{`@${getUsername()}`} {`@${data?.user_info.username}`}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -1,12 +1,14 @@
import { Skeleton, Text } from "@radix-ui/themes"; import { HoverCard, Link, Skeleton, Text } from "@radix-ui/themes";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Link } from "react-router-dom"; import { lazy, Suspense } from "react";
import { axiosLocalhost } from "../../api/axios/axios"; import { axiosLocalhost } from "../../api/axios/axios";
type TUserNicknameLink = { type TUserNicknameLink = {
userId: string; userId: string;
}; };
const UserCard = lazy(() => import("../UserCard/UserCard"));
export default function UserNicknameLink(props: TUserNicknameLink) { export default function UserNicknameLink(props: TUserNicknameLink) {
const { data, isPending } = useQuery({ const { data, isPending } = useQuery({
queryKey: [`userLink${props.userId}`], queryKey: [`userLink${props.userId}`],
@ -21,13 +23,38 @@ export default function UserNicknameLink(props: TUserNicknameLink) {
if (isPending) if (isPending)
return ( return (
<Skeleton> <Skeleton>
<Text>@Nickname</Text> <Text
size={{
sm: "4",
md: "5",
lg: "6",
}}
>
@Nickname
</Text>
</Skeleton> </Skeleton>
); );
return ( return (
<Link to={`/users/${data}`}> <HoverCard.Root>
<Text>@{data}</Text> <HoverCard.Trigger>
<Link href={`/users/${data}`}>
<Text
size={{
sm: "3",
md: "4",
lg: "5",
}}
>
@{data}
</Text>
</Link> </Link>
</HoverCard.Trigger>
<HoverCard.Content className="p-0" maxWidth={'220px'}>
<Suspense fallback={<Skeleton />}>
<UserCard userId={props.userId} />
</Suspense>
</HoverCard.Content>
</HoverCard.Root>
); );
} }

View File

@ -0,0 +1,50 @@
import { Inset } from "@radix-ui/themes";
import { useMemo } from "react";
type TInsetImageProps = {
isHovered: boolean;
ref_: React.RefObject<HTMLDivElement>;
windowWidth: number;
};
export default function InsetImage(props: TInsetImageProps) {
const seed = useMemo(() => {
return Math.floor(Math.random() * (1 + Math.random()) * 100000);
}, []);
return (
<Inset
side={"left"}
clip={"padding-box"}
className={`max-w-[${
props.isHovered ? "100%" : "225px"
}] transition-[flex] duration-[250ms]
${props.isHovered ? "flex-1" : "flex-[0.5]"}
relative overflow-hidden h-72`}
>
<img
style={{
minWidth: `${
Math.min(
props.windowWidth,
props.ref_.current?.clientWidth || 0
) / 2
}px`,
transform: `${
props.isHovered
? "translateX(0)"
: `translateX(calc(-50% + ${
Math.min(
props.windowWidth,
props.ref_.current?.clientWidth || 0
) / 6
}px))`
}`,
}}
className={`h-72 transition-all duration-[250ms]`}
src={`https://picsum.photos/seed/${seed}/1000/600?grayscale`}
alt="Bold typography"
/>
</Inset>
);
}

View File

@ -1,24 +1,20 @@
import { CalendarIcon } from "@radix-ui/react-icons"; import { CalendarIcon } from "@radix-ui/react-icons";
import { import { Box, Card, Flex, Heading, Text, Tooltip } from "@radix-ui/themes";
Card,
Flex,
Heading,
Inset,
Text,
Tooltip
} from "@radix-ui/themes";
import dayjs from "dayjs"; import dayjs from "dayjs";
import "dayjs/locale/ru"; import "dayjs/locale/ru";
import { useEffect, useMemo, useRef, useState } from "react"; import { Interweave } from "interweave";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Post } from "../../../@types/PostTypes"; import { Post } from "../../../@types/PostTypes";
import UserNicknameLink from "../../../Components/UserNicknameLink/UserNicknameLink";
import InsetImage from "./InsetImage/InsetImage";
export default function PostCard(props: Post) { export default function PostCard(props: Post) {
const navigate = useNavigate(); const navigate = useNavigate();
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [windowWidth, setWindowWidth] = useState<number>(window.innerWidth); const [windowWidth, setWindowWidth] = useState<number>(window.innerWidth);
const [isImageLoaded, setIsImageLoaded] = useState(false);
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const parsedDate = dayjs(props.created_at) const parsedDate = dayjs(props.created_at)
@ -29,72 +25,74 @@ export default function PostCard(props: Post) {
navigate(`/posts/${props.post_id.toString()}`); navigate(`/posts/${props.post_id.toString()}`);
}; };
const seed = useMemo(() => {
return Math.floor(Math.random() * (1 + Math.random()) * 100000);
}, []);
useEffect(() => { useEffect(() => {
const f = () => { const f = () => {
setWindowWidth(window.innerWidth); setWindowWidth(window.innerWidth);
console.log(`Window width: ${window.innerWidth} px`); console.log(`Window width: ${window.innerWidth} px`);
};
} f();
f() window.addEventListener("resize", f);
return () => window.removeEventListener("resize", f);
window.addEventListener('resize', f);
return () => window.removeEventListener('resize', f);
}, []); }, []);
return ( return (
<Card <Card
ref={ref} ref={ref}
className="flex w-full cursor-pointer" className="flex w-full cursor-pointer max-h-72"
onClick={clickHandler} onClick={clickHandler}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
<Inset <InsetImage
side={"left"} isHovered={isHovered}
clip={"padding-box"} ref_={ref}
className={`max-w-[${ windowWidth={windowWidth}
isHovered ? "100%" : "225px"
}] transition-all duration-[250ms]
${
isHovered ? "flex-1" : "flex-[0.5]"
}
relative overflow-hidden h-72`}
>
<img
style={{
minWidth: `${Math.min(windowWidth, ref.current?.clientWidth || 0) / 2}px`,
transform: `${
isHovered ? "translateX(0)" : `translateX(calc(-50% + ${Math.min(windowWidth, ref.current?.clientWidth || 0) / 6}px))`
}`
}}
className={`h-72 transition-all duration-[250ms]`}
src={`https://picsum.photos/seed/${seed}/1000/600?grayscale`}
alt="Bold typography"
onLoadedData={() => setIsImageLoaded(true)}
/> />
</Inset>
<Flex <Flex
direction={"column"} direction={"column"}
className="justify-between flex-1 w-full px-4 pt-2" className="justify-between flex-1 w-full gap-4 px-4 pt-2"
> >
<Heading>{props.title}</Heading> <Heading
size={{
sm: "4",
md: "5",
lg: "6",
}}
className="flex items-center h-fit"
>
{props.title}
</Heading>
<Flex gap={"2"} className="items-end justify-start flex-1"> <Flex
<Tooltip content={`Written at`}> direction={"column"}
<Flex className="items-center gap-2"> justify={"between"}
<CalendarIcon className="size-6" /> className="h-full overflow-hidden"
>
<Box className="overflow-y-hidden">
<Text <Text
size={{ size={{
sm: "4", sm: "4",
md: "5", md: "5",
lg: "6", lg: "6",
}} }}
>
<Interweave content={props.content} />
</Text>
</Box>
<Flex className="gap-2 lg:gap-4">
<Tooltip content={`Written at`}>
<Flex className="items-center gap-2 h-fit">
<CalendarIcon className="size-6" />
<Text
size={{
sm: "3",
md: "4",
lg: "5",
}}
weight={"medium"} weight={"medium"}
className="flex items-center gap-1" className="flex items-center gap-1"
> >
@ -102,6 +100,20 @@ export default function PostCard(props: Post) {
</Text> </Text>
</Flex> </Flex>
</Tooltip> </Tooltip>
<Flex className="items-center gap-2 h-fit">
<Text
size={{
sm: "3",
md: "4",
lg: "5",
}}
>
Author:{" "}
</Text>
<UserNicknameLink userId={props.user_id} />
</Flex>
</Flex>
</Flex> </Flex>
</Flex> </Flex>
</Card> </Card>

View File

@ -43,7 +43,7 @@ export default function RandomPostsPage() {
direction={"column"} direction={"column"}
className="w-full overflow-hidden sm:mx-auto max-w-pc-width " className="w-full overflow-hidden sm:mx-auto max-w-pc-width "
> >
<Flex direction={"column"} gap={"4"}> <Flex direction={"column"} gap={"4"} className="mx-4 xl:mx-0">
{data?.pages.map((post, i) => { {data?.pages.map((post, i) => {
return ( return (
<Flex <Flex

View File

@ -16,7 +16,7 @@
} }
.radix-themes { .radix-themes {
--default-font-family: "Pochaevsk", sans-serif; --default-font-family:
--heading-font-family: "Edu AU VIC WA NT Pre", cursive; --heading-font-family: "Edu AU VIC WA NT Pre", cursive;
/* Your custom font for <Heading> components */ /* Your custom font for <Heading> components */
--code-font-family: --code-font-family:

View File

@ -66,7 +66,7 @@ export default function MainPage() {
<NavBar /> <NavBar />
<Box <Box
flexGrow={"1"} flexGrow={"1"}
className="flex flex-col mx-4 overflow-hidden" className="flex flex-col overflow-hidden"
> >
<Outlet /> <Outlet />
</Box> </Box>

View File

@ -160,7 +160,7 @@ filtered_posts AS (
json_build_object( json_build_object(
'post_id', post_id::text, 'blog_id', blog_id::text, 'post_id', post_id::text, 'blog_id', blog_id::text,
'user_id', user_id::text, 'title', title, 'user_id', user_id::text, 'title', title,
'created_at', created_at 'created_at', created_at, 'content', SUBSTRING(content FROM 1 FOR 300)
) )
FROM FROM
public.posts public.posts

View File

@ -49,7 +49,7 @@ filtered_posts AS (
json_build_object( json_build_object(
'post_id', post_id::text, 'blog_id', blog_id::text, 'post_id', post_id::text, 'blog_id', blog_id::text,
'user_id', user_id::text, 'title', title, 'user_id', user_id::text, 'title', title,
'created_at', created_at 'created_at', created_at, 'content', SUBSTRING(content FROM 1 FOR 300)
) )
FROM FROM
public.posts public.posts