Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2ffaa3c

Browse files
feat: init database
1 parent 563ae5b commit 2ffaa3c

File tree

23 files changed

+1362
-654
lines changed

23 files changed

+1362
-654
lines changed

‎apps/web/src/actions/auth/index.ts‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const signInWithCredentials = async (email: string, password: string) =>
1919
}
2020

2121
export const signInWithGithub = async () => {
22+
console.log("signInWithGithub...")
2223
await signIn("github")
2324
}
2425

@@ -38,14 +39,11 @@ export const signUp = async (
3839
const hashedPassword = await bcryptjs.hash(password, 10)
3940

4041
await createUser({
41-
data: {
42-
email,
43-
password: hashedPassword,
44-
},
42+
email,
43+
password: hashedPassword,
44+
username: email.split("@")[0],
4545
})
4646

47-
console.log("create user...successfully")
48-
4947
// create verification code
5048
const token = crypto.randomUUID()
5149
await prisma.verificationToken.create({
@@ -56,8 +54,6 @@ export const signUp = async (
5654
},
5755
})
5856

59-
console.log("verification token...successfully")
60-
6157
// send email
6258
await sendEmail({
6359
email,
@@ -68,7 +64,6 @@ export const signUp = async (
6864
}),
6965
})
7066
} catch (error) {
71-
console.error("signUp.error", error)
7267
if (error?.error?.code === "P2002") {
7368
return {
7469
formErrors: null,

‎apps/web/src/app/[lang]/(protected)/user/posts/page.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export async function generateMetadata(): Promise<Metadata> {
1111
const user = await getUser({ where: { id: session?.user?.id } })
1212

1313
return {
14-
title: `Posts - ${user?.data?.name}`,
15-
description: `Posts of ${user?.data?.name}`,
14+
title: `Posts - ${user?.data?.username}`,
15+
description: `Posts of ${user?.data?.username}`,
1616
}
1717
}
1818

‎apps/web/src/app/[lang]/(public)/page.tsx‎

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
11
import { Suspense } from "react"
22
import { Metadata } from "next"
33

4-
import { PostStatus } from "database"
5-
import { PostSkeleton } from "ui"
4+
import { Typography } from "ui"
65

7-
import Filter from "@/molecules/home/filter"
8-
import PostList from "@/molecules/posts/post-list"
6+
import PostPagination from "@/molecules/posts/post-pagination"
97

108
export const metadata: Metadata = {
11-
title: "Next-forum - Share the best things",
12-
description: "Share the best things in the world",
9+
title: "Home",
10+
description: "Welcome to our community forum",
1311
}
1412

15-
export default asyncfunction Page() {
13+
export default function HomePage() {
1614
return (
17-
<div>
18-
<Filter />
19-
<Suspense fallback={<PostSkeleton total={10} />}>
20-
<PostList
21-
getPostParams={{
22-
postStatus: PostStatus.PUBLISHED,
23-
}}
24-
/>
15+
<div className="container py-6">
16+
<Suspense fallback={<div className="py-8 text-center">Loading posts...</div>}>
17+
<PostPagination />
2518
</Suspense>
2619
</div>
2720
)

‎apps/web/src/molecules/posts/post-list/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { useCallback, useState } from "react"
44
import { useParams } from "next/navigation"
55

6-
import { getPosts, TGetPostsRequest,TPostItem } from "database"
6+
import { getPosts, TPostItem } from "database"
77
import { cn } from "ui"
88

99
import InfiniteScroll from "@/molecules/infinite-scroll"
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"use client"
2+
3+
import React, { useCallback, useState } from "react"
4+
import { useRouter, useSearchParams } from "next/navigation"
5+
6+
import { getPosts, TPostItem } from "database"
7+
import {
8+
cn,
9+
Pagination,
10+
PaginationContent,
11+
PaginationEllipsis,
12+
PaginationItem,
13+
PaginationLink,
14+
PaginationNext,
15+
PaginationPrevious,
16+
} from "ui"
17+
18+
import PostItem from "../post-item"
19+
20+
export type TPostPaginationProps = {
21+
containerClassName?: string
22+
itemsPerPage?: number
23+
}
24+
25+
export default function PostPagination({
26+
containerClassName,
27+
itemsPerPage = 10,
28+
}: TPostPaginationProps) {
29+
const router = useRouter()
30+
const searchParams = useSearchParams()
31+
const currentPage = Number(searchParams.get("page") || 1)
32+
33+
const [isLoading, setIsLoading] = useState(false)
34+
const [posts, setPosts] = useState<TPostItem[]>([])
35+
const [totalPages, setTotalPages] = useState(1)
36+
37+
// Fetch posts when the page changes
38+
React.useEffect(() => {
39+
async function fetchPosts() {
40+
setIsLoading(true)
41+
try {
42+
const { data } = await getPosts({
43+
skip: (currentPage - 1) * itemsPerPage,
44+
take: itemsPerPage,
45+
})
46+
47+
setPosts(data?.data || [])
48+
setTotalPages(data?.totalPages || 1)
49+
} catch (error) {
50+
console.error("Error fetching posts:", error)
51+
} finally {
52+
setIsLoading(false)
53+
}
54+
}
55+
56+
fetchPosts()
57+
}, [currentPage, itemsPerPage])
58+
59+
// Handle page change
60+
const handlePageChange = useCallback(
61+
(page: number) => {
62+
const params = new URLSearchParams(searchParams.toString())
63+
params.set("page", page.toString())
64+
router.push(`?${params.toString()}`)
65+
},
66+
[router, searchParams]
67+
)
68+
69+
// Generate pagination items
70+
const renderPaginationItems = useCallback(() => {
71+
const items = []
72+
const maxVisiblePages = 5
73+
74+
// Always show first page
75+
items.push(
76+
<PaginationItem key="page-1">
77+
<PaginationLink
78+
href="#"
79+
onClick={(e) => {
80+
e.preventDefault()
81+
handlePageChange(1)
82+
}}
83+
isActive={currentPage === 1}
84+
>
85+
1
86+
</PaginationLink>
87+
</PaginationItem>
88+
)
89+
90+
// Calculate range of visible pages
91+
let startPage = Math.max(2, currentPage - Math.floor(maxVisiblePages / 2))
92+
let endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 3)
93+
94+
// Adjust if we're near the end
95+
if (endPage <= startPage) {
96+
endPage = Math.min(totalPages - 1, startPage + 1)
97+
}
98+
99+
// Show ellipsis if needed before middle pages
100+
if (startPage > 2) {
101+
items.push(
102+
<PaginationItem key="ellipsis-1">
103+
<PaginationEllipsis />
104+
</PaginationItem>
105+
)
106+
}
107+
108+
// Add middle pages
109+
for (let i = startPage; i <= endPage; i++) {
110+
items.push(
111+
<PaginationItem key={`page-${i}`}>
112+
<PaginationLink
113+
href="#"
114+
onClick={(e) => {
115+
e.preventDefault()
116+
handlePageChange(i)
117+
}}
118+
isActive={currentPage === i}
119+
>
120+
{i}
121+
</PaginationLink>
122+
</PaginationItem>
123+
)
124+
}
125+
126+
// Show ellipsis if needed after middle pages
127+
if (endPage < totalPages - 1) {
128+
items.push(
129+
<PaginationItem key="ellipsis-2">
130+
<PaginationEllipsis />
131+
</PaginationItem>
132+
)
133+
}
134+
135+
// Always show last page if there is more than one page
136+
if (totalPages > 1) {
137+
items.push(
138+
<PaginationItem key={`page-${totalPages}`}>
139+
<PaginationLink
140+
href="#"
141+
onClick={(e) => {
142+
e.preventDefault()
143+
handlePageChange(totalPages)
144+
}}
145+
isActive={currentPage === totalPages}
146+
>
147+
{totalPages}
148+
</PaginationLink>
149+
</PaginationItem>
150+
)
151+
}
152+
153+
return items
154+
}, [currentPage, totalPages, handlePageChange])
155+
156+
return (
157+
<div className={cn("space-y-6", containerClassName)}>
158+
{isLoading ? (
159+
<div className="flex justify-center py-8">
160+
<div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
161+
</div>
162+
) : (
163+
<>
164+
{posts.length === 0 ? (
165+
<div className="flex justify-center py-8">
166+
<p className="text-muted-foreground">No posts found</p>
167+
</div>
168+
) : (
169+
<div className="space-y-4">
170+
{posts.map((post) => (
171+
<PostItem
172+
key={post.id}
173+
post={post}
174+
/>
175+
))}
176+
</div>
177+
)}
178+
179+
{totalPages > 1 && (
180+
<Pagination>
181+
<PaginationContent>
182+
<PaginationItem>
183+
<PaginationPrevious
184+
href="#"
185+
onClick={(e) => {
186+
e.preventDefault()
187+
if (currentPage > 1) {
188+
handlePageChange(currentPage - 1)
189+
}
190+
}}
191+
className={cn(currentPage <= 1 && "pointer-events-none opacity-50")}
192+
/>
193+
</PaginationItem>
194+
195+
{renderPaginationItems()}
196+
197+
<PaginationItem>
198+
<PaginationNext
199+
href="#"
200+
onClick={(e) => {
201+
e.preventDefault()
202+
if (currentPage < totalPages) {
203+
handlePageChange(currentPage + 1)
204+
}
205+
}}
206+
className={cn(currentPage >= totalPages && "pointer-events-none opacity-50")}
207+
/>
208+
</PaginationItem>
209+
</PaginationContent>
210+
</Pagination>
211+
)}
212+
</>
213+
)}
214+
</div>
215+
)
216+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /