Infinite scrolling setup
This commit is contained in:
parent
facaa96955
commit
3bb21f67e9
16
enshi/package-lock.json
generated
16
enshi/package-lock.json
generated
@ -33,6 +33,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-intersection-observer": "^9.15.1",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.26.2"
|
||||
},
|
||||
@ -6486,6 +6487,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-intersection-observer": {
|
||||
"version": "9.15.1",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.15.1.tgz",
|
||||
"integrity": "sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-intersection-observer": "^9.15.1",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.26.2"
|
||||
},
|
||||
|
||||
@ -4,10 +4,24 @@ export type GetRandomPostsRow = {
|
||||
user_id: string;
|
||||
title: string;
|
||||
// created_at: Date;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export type TPostData = {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type Post = {
|
||||
blog_id: string | null;
|
||||
created_at: string; // ISO 8601 date string
|
||||
post_id: string;
|
||||
title: string;
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
export type SelectedPostsResponse = {
|
||||
selected_posts: Post[];
|
||||
has_next_page: boolean;
|
||||
next_page_index: number;
|
||||
prev_page_index: number;
|
||||
};
|
||||
|
||||
@ -1,66 +1,90 @@
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { Container, Flex, Heading, Separator } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Flex,
|
||||
Heading,
|
||||
ScrollArea,
|
||||
Separator,
|
||||
} from "@radix-ui/themes";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GetRandomPostsRow } from "../../@types/PostTypes";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { SelectedPostsResponse } from "../../@types/PostTypes";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import PostCard from "./PostCard/PostCard";
|
||||
|
||||
const LIMIT = 10;
|
||||
const LIMIT = 3;
|
||||
|
||||
export default function RandomPostsPage() {
|
||||
const {t} = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ["random_posts_key"],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const [ref, inView] = useInView();
|
||||
|
||||
const { data, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||||
queryKey: [`random_post_inf`],
|
||||
queryFn: async ({ pageParam }): Promise<SelectedPostsResponse> => {
|
||||
const response = await axiosLocalhost.get(
|
||||
`/posts/random?limit=${LIMIT}`
|
||||
`/posts/random?limit=${LIMIT}&offset=${pageParam}`
|
||||
);
|
||||
|
||||
return response.data as GetRandomPostsRow[];
|
||||
} catch (error) {
|
||||
console.log(`Something went wrong`);
|
||||
}
|
||||
|
||||
return [];
|
||||
return response.data as SelectedPostsResponse;
|
||||
},
|
||||
initialPageParam: 0,
|
||||
getPreviousPageParam: (lastPage) =>
|
||||
lastPage.prev_page_index < 0 ? undefined : lastPage.prev_page_index,
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.next_page_index < 0 ? undefined : lastPage.next_page_index,
|
||||
});
|
||||
|
||||
// const { data, refetch } = useQuery({
|
||||
// queryKey: ["random_posts_key"],
|
||||
// queryFn: async () => {
|
||||
// try {
|
||||
// const response = await axiosLocalhost.get(
|
||||
// `/posts/random?limit=${LIMIT}&offset=0`
|
||||
// );
|
||||
|
||||
// return response.data as GetRandomPostsRow[];
|
||||
// } catch (error) {
|
||||
// console.log(`Something went wrong`);
|
||||
// }
|
||||
|
||||
// return [];
|
||||
// },
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
console.log(`Loading more posts...`);
|
||||
if (hasNextPage) fetchNextPage();
|
||||
}
|
||||
}, [inView]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex direction={"column"} className="mx-auto">
|
||||
<Flex direction={"column"} className="mx-auto overflow-hidden">
|
||||
<Heading size={"9"} weight={"regular"} className="text-center">
|
||||
{t("discover")}
|
||||
</Heading>
|
||||
|
||||
<Separator size={"4"} className="my-8" />
|
||||
|
||||
<ScrollArea.Root className="w-full h-full overflow-hidden">
|
||||
<ScrollArea.Viewport className="overflow-scroll rounded size-full">
|
||||
{data?.map((post, i) => {
|
||||
<ScrollArea>
|
||||
{data?.pages.map((post, i) => {
|
||||
return (
|
||||
<>
|
||||
{post.selected_posts.map((post) => {
|
||||
return (
|
||||
<Container size={"3"} key={`post${i}`}>
|
||||
<PostCard post={post} />
|
||||
</Container>
|
||||
);
|
||||
})}
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="z-50 flex touch-none select-none p-0.5 w-2"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-slate-200"/>
|
||||
</ScrollArea.Scrollbar>
|
||||
{/* <ScrollArea.Scrollbar
|
||||
className="flex touch-none select-none bg-blackA3 p-0.5 transition-colors duration-[160ms] ease-out hover:bg-blackA5 data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-mauve10 before:absolute before:left-1/2 before:top-1/2 before:size-full before:min-h-[44px] before:min-w-[44px] before:-translate-x-1/2 before:-translate-y-1/2" />
|
||||
</ScrollArea.Scrollbar> */}
|
||||
{/* <ScrollArea.Corner className="bg-blackA5" /> */}
|
||||
</ScrollArea.Root>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
<Box ref={ref}>qqqqqqqqqqqq</Box>
|
||||
</ScrollArea>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -146,45 +146,70 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRandomPosts = `-- name: GetRandomPosts :many
|
||||
SELECT post_id, blog_id, user_id, title, created_at
|
||||
FROM public.posts
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1
|
||||
const getRandomPosts = `-- name: GetRandomPosts :one
|
||||
WITH all_posts AS (
|
||||
SELECT
|
||||
COUNT(*) AS post_count
|
||||
FROM
|
||||
public.posts
|
||||
),
|
||||
filtered_posts AS (
|
||||
SELECT
|
||||
ARRAY(
|
||||
SELECT
|
||||
json_build_object(
|
||||
'post_id', post_id::text, 'blog_id', blog_id::text,
|
||||
'user_id', user_id::text, 'title', title,
|
||||
'created_at', created_at
|
||||
)
|
||||
FROM
|
||||
public.posts
|
||||
ORDER BY
|
||||
created_at DESC
|
||||
LIMIT $1 OFFSET $3
|
||||
) as selected_posts
|
||||
)
|
||||
SELECT
|
||||
fp.selected_posts,
|
||||
(ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page,
|
||||
case
|
||||
when (ap.post_count - ($2 + 1) * $1)::int > 0
|
||||
then $2 + 1
|
||||
else -1
|
||||
end as next_page_index,
|
||||
case
|
||||
when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count
|
||||
then $2 - 1
|
||||
else -1
|
||||
end as prev_page_index
|
||||
FROM
|
||||
filtered_posts fp,
|
||||
all_posts ap
|
||||
`
|
||||
|
||||
type GetRandomPostsRow struct {
|
||||
PostID int64 `json:"post_id"`
|
||||
BlogID pgtype.Int8 `json:"blog_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
type GetRandomPostsParams struct {
|
||||
Column1 interface{} `json:"column_1"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetRandomPosts(ctx context.Context, limit int32) ([]GetRandomPostsRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRandomPosts, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
type GetRandomPostsRow struct {
|
||||
SelectedPosts interface{} `json:"selected_posts"`
|
||||
HasNextPage bool `json:"has_next_page"`
|
||||
NextPageIndex int32 `json:"next_page_index"`
|
||||
PrevPageIndex int32 `json:"prev_page_index"`
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetRandomPostsRow
|
||||
for rows.Next() {
|
||||
|
||||
func (q *Queries) GetRandomPosts(ctx context.Context, arg GetRandomPostsParams) (GetRandomPostsRow, error) {
|
||||
row := q.db.QueryRow(ctx, getRandomPosts, arg.Column1, arg.Column2, arg.Offset)
|
||||
var i GetRandomPostsRow
|
||||
if err := rows.Scan(
|
||||
&i.PostID,
|
||||
&i.BlogID,
|
||||
&i.UserID,
|
||||
&i.Title,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
err := row.Scan(
|
||||
&i.SelectedPosts,
|
||||
&i.HasNextPage,
|
||||
&i.NextPageIndex,
|
||||
&i.PrevPageIndex,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updatePostBlogId = `-- name: UpdatePostBlogId :exec
|
||||
|
||||
@ -35,8 +35,42 @@ SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE post_id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetRandomPosts :many
|
||||
SELECT post_id, blog_id, user_id, title, created_at
|
||||
FROM public.posts
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1;
|
||||
-- name: GetRandomPosts :one
|
||||
WITH all_posts AS (
|
||||
SELECT
|
||||
COUNT(*) AS post_count
|
||||
FROM
|
||||
public.posts
|
||||
),
|
||||
filtered_posts AS (
|
||||
SELECT
|
||||
ARRAY(
|
||||
SELECT
|
||||
json_build_object(
|
||||
'post_id', post_id::text, 'blog_id', blog_id::text,
|
||||
'user_id', user_id::text, 'title', title,
|
||||
'created_at', created_at
|
||||
)
|
||||
FROM
|
||||
public.posts
|
||||
ORDER BY
|
||||
created_at DESC
|
||||
LIMIT $1 OFFSET $3
|
||||
) as selected_posts
|
||||
)
|
||||
SELECT
|
||||
fp.selected_posts,
|
||||
(ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page,
|
||||
case
|
||||
when (ap.post_count - ($2 + 1) * $1)::int > 0
|
||||
then $2 + 1
|
||||
else -1
|
||||
end as next_page_index,
|
||||
case
|
||||
when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count
|
||||
then $2 - 1
|
||||
else -1
|
||||
end as prev_page_index
|
||||
FROM
|
||||
filtered_posts fp,
|
||||
all_posts ap;
|
||||
|
||||
@ -19,25 +19,38 @@ func GetRandomPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
postsData, err :=
|
||||
db_repo.New(db_connection.Dbx).
|
||||
GetRandomPosts(context.Background(), int32(limit))
|
||||
offset, err := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]any, 0)
|
||||
|
||||
for _, post := range postsData {
|
||||
result = append(result, gin.H{
|
||||
"post_id": strconv.Itoa(int(post.PostID)),
|
||||
"title": post.Title,
|
||||
"user_id": strconv.Itoa(int(post.UserID)),
|
||||
})
|
||||
params := db_repo.GetRandomPostsParams{
|
||||
Column1: int32(limit),
|
||||
Column2: int32(offset),
|
||||
Offset: int32(offset * limit),
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, result)
|
||||
postsData, err :=
|
||||
db_repo.New(db_connection.Dbx).
|
||||
GetRandomPosts(context.Background(), params)
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// result := make([]any, 0)
|
||||
|
||||
// for _, post := range postsData {
|
||||
// result = append(result, gin.H{
|
||||
// "post_id": strconv.Itoa(int(post.PostID)),
|
||||
// "title": post.Title,
|
||||
// "user_id": strconv.Itoa(int(post.UserID)),
|
||||
// })
|
||||
// }
|
||||
|
||||
c.IndentedJSON(http.StatusOK, postsData)
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user