Post card improvement
This commit is contained in:
parent
d1474bce35
commit
1659e007e4
@ -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 = {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user