Skip to content

Commit 2ffaa3c

Browse files
committed
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 async function 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)