Skip to content

Commit a8470ff

Browse files
committed
feat: add landing page, add prisma orm
1 parent 576be28 commit a8470ff

File tree

10 files changed

+401
-96
lines changed

10 files changed

+401
-96
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ yarn-error.log*
2727

2828
# local env files
2929
.env*.local
30+
.env
3031

3132
# vercel
3233
.vercel

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
"start": "next start",
99
"lint": "lint-staged && next lint",
1010
"test": "echo \"Error: no test specified\" && exit 1",
11-
"prepare": "husky"
11+
"prepare": "husky",
12+
"seed": "tsx prisma/seed.ts"
1213
},
1314
"lint-staged": {
1415
"*.{ts,tsx}": "eslint --fix",
1516
"*.{ts,tsx,css,md}": "prettier --write"
1617
},
1718
"dependencies": {
19+
"@prisma/client": "^5.22.0",
1820
"@radix-ui/react-avatar": "^1.1.1",
1921
"@radix-ui/react-dialog": "^1.1.2",
2022
"@radix-ui/react-dropdown-menu": "^2.1.2",
@@ -28,6 +30,7 @@
2830
"lucide-react": "^0.460.0",
2931
"next": "15.0.3",
3032
"next-themes": "^0.4.3",
33+
"prisma": "^5.22.0",
3134
"react": "^18.3.1",
3235
"react-dom": "^18.3.1",
3336
"tailwind-merge": "^2.5.4",

pnpm-lock.yaml

+66
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/schema.prisma

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
generator client {
2+
provider = "prisma-client-js"
3+
}
4+
5+
datasource db {
6+
provider = "postgresql"
7+
url = env("DATABASE_URL")
8+
}
9+
10+
model Post {
11+
id String @id @default(cuid())
12+
avatar String
13+
nickname String
14+
date DateTime
15+
content String
16+
imageList String[]
17+
videoList String[]
18+
createdAt DateTime @default(now())
19+
updatedAt DateTime @updatedAt
20+
}

prisma/seed.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { PrismaClient } from "@prisma/client";
2+
3+
const prisma = new PrismaClient();
4+
5+
async function main() {
6+
// 清理现有数据
7+
await prisma.post.deleteMany();
8+
9+
// 创建测试数据
10+
const posts = [
11+
{
12+
avatar: "https://github.com/wangrunlin.png",
13+
nickname: "Leo Wang",
14+
date: new Date("2024-03-28T20:44:40"),
15+
content:
16+
"Just finished an amazing coding session! Built some cool new features for my latest project. Love how the UI is coming together. What do you think about these design inspirations? 🚀 #coding #webdev",
17+
imageList: [
18+
"https://images.unsplash.com/photo-1711834231479-5f6d4556d6f3",
19+
"https://images.unsplash.com/photo-1498050108023-c5249f4df085",
20+
"https://images.unsplash.com/photo-1517694712202-14dd9538aa97",
21+
],
22+
videoList: [],
23+
},
24+
{
25+
avatar: "https://github.com/wangrunlin.png",
26+
nickname: "阿林",
27+
date: new Date("2024-03-28T20:58:40"),
28+
content:
29+
"春天来了,公园里的樱花开得真美!周末和朋友一起去野餐,度过了愉快的下午。分享一些照片,希望这份美好也能传递给大家 🌸 #春天 #樱花 #周末",
30+
imageList: [
31+
"https://images.unsplash.com/photo-1522383225653-ed111181a951",
32+
"https://images.unsplash.com/photo-1516353302158-d8ea39485544",
33+
"https://images.unsplash.com/photo-1518563222397-1875011bbf5a",
34+
"https://images.unsplash.com/photo-1519674908016-15876737f0f7",
35+
],
36+
videoList: [],
37+
},
38+
];
39+
40+
for (const post of posts) {
41+
await prisma.post.create({
42+
data: post,
43+
});
44+
}
45+
46+
console.log("Seed data created successfully");
47+
}
48+
49+
main()
50+
.catch((e) => {
51+
console.error(e);
52+
process.exit(1);
53+
})
54+
.finally(async () => {
55+
await prisma.$disconnect();
56+
});

src/app/explore/page.tsx

+67-92
Original file line numberDiff line numberDiff line change
@@ -8,105 +8,80 @@ import {
88
CardHeader,
99
CardTitle,
1010
} from "@/components/ui/card";
11+
import { prisma } from "@/lib/prisma";
1112

12-
type CardType = {
13-
id: string;
14-
avatar?: string;
15-
nickname?: string;
16-
date?: string;
17-
content?: string;
18-
imageList?: string[];
19-
videoList?: string[];
20-
};
13+
export default async function Home() {
14+
const posts = await prisma.post.findMany({ orderBy: { date: "desc" } });
2115

22-
const contentList: CardType[] = [
23-
{
24-
id: "1",
25-
avatar: "https://github.com/wangrunlin.png",
26-
nickname: "Leo Wang",
27-
date: "2024/6/28 20:44:40",
28-
content: "Card Content",
29-
imageList: [
30-
// generate 3 - 5 unsplash images link
31-
"https://images.unsplash.com/photo-1711834231479-5f6d4556d6f3",
32-
"https://plus.unsplash.com/premium_photo-1701094772268-842fcc7ce510?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MXx8fGVufDB8fHx8fA%3D%3D",
33-
"https://images.unsplash.com/photo-1712009508464-8a41723abf63?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTR8fHxlbnwwfHx8fHw%3D",
34-
],
35-
videoList: [""],
36-
},
37-
{
38-
id: "2",
39-
avatar: "https://github.com/wangrunlin.png",
40-
nickname: "阿林",
41-
date: "2024/6/28 20:58:40",
42-
content: "Card Content 2",
43-
imageList: [
44-
// generate 3 - 5 unsplash images link
45-
"https://images.unsplash.com/photo-1719420062178-8675f842252a?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90b3MtZmVlZHwyM3x8fGVufDB8fHx8fA%3D%3D",
46-
"https://plus.unsplash.com/premium_photo-1718169684197-adf733b5c7ef?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90b3MtZmVlZHwyMXx8fGVufDB8fHx8fA%3D%3D",
47-
"https://images.unsplash.com/photo-1711834231479-5f6d4556d6f3",
48-
// "https://plus.unsplash.com/premium_photo-1701094772268-842fcc7ce510?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MXx8fGVufDB8fHx8fA%3D%3D",
49-
// "https://images.unsplash.com/photo-1712009508464-8a41723abf63?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTR8fHxlbnwwfHx8fHw%3D",
50-
],
51-
videoList: [""],
52-
},
53-
];
54-
55-
export default function Home() {
5616
return (
5717
<main className="mt-16 mx-4 md:mx-24 lg:mx-48">
5818
<TypographyH1 className="my-8">This Moment</TypographyH1>
59-
<div className="space-y-8 mb-8">
60-
{contentList.map(
61-
({ avatar, nickname, date, content, imageList, videoList }) => (
62-
<Card key={avatar}>
63-
<CardHeader>
64-
<CardTitle className="flex items-center space-x-2">
65-
<Avatar>
66-
<AvatarImage src={avatar} />
67-
<AvatarFallback>
68-
{nickname?.at(-1)?.toUpperCase()}
69-
{/* todo)) 更新为英文名称缩写或者中文最后一个字 */}
70-
</AvatarFallback>
71-
</Avatar>
72-
<span className="text-xl text-muted-foreground">
73-
{nickname}
74-
</span>
75-
</CardTitle>
76-
<CardDescription>
77-
{new Date(date || "").toLocaleString("zh-CN")}
78-
</CardDescription>
79-
</CardHeader>
80-
<CardContent>{content}</CardContent>
81-
<CardFooter className="flex-col items-start">
82-
{imageList?.length &&
83-
(imageList.length > 2 ? (
84-
<div className="flex space-x-4 ms-0">
85-
{imageList.map((image) => (
86-
<div key={image} className="">
87-
{/* eslint-disable-next-line @next/next/no-img-element */}
88-
<img
89-
src={image}
90-
alt="#"
91-
className="object-cover max-h-96"
92-
/>
93-
</div>
94-
))}
95-
</div>
96-
) : (
97-
<div>
98-
{imageList.map((image) => (
99-
// eslint-disable-next-line @next/next/no-img-element
100-
<img key={image} src={image} alt="#" />
101-
))}
19+
<div className="space-y-6 mb-4">
20+
{posts.map(({ id, avatar, nickname, date, content, imageList }) => (
21+
<Card
22+
key={id}
23+
className="hover:shadow-lg transition-shadow duration-200"
24+
>
25+
<CardHeader>
26+
<CardTitle className="flex items-center space-x-3">
27+
<Avatar className="h-12 w-12">
28+
<AvatarImage src={avatar} />
29+
<AvatarFallback>
30+
{/^[A-Za-z]/.test(nickname || "")
31+
? nickname
32+
?.split(" ")
33+
.map((n) => n[0])
34+
.join("")
35+
.toUpperCase()
36+
: nickname?.slice(-1)}
37+
</AvatarFallback>
38+
</Avatar>
39+
<span className="text-xl font-medium">{nickname}</span>
40+
</CardTitle>
41+
<CardDescription className="mt-1 text-sm">
42+
{new Date(date || "").toLocaleString("zh-CN", {
43+
year: "numeric",
44+
month: "long",
45+
day: "numeric",
46+
hour: "2-digit",
47+
minute: "2-digit",
48+
})}
49+
</CardDescription>
50+
</CardHeader>
51+
<CardContent>
52+
<p className="whitespace-pre-wrap text-base">{content}</p>
53+
</CardContent>
54+
<CardFooter className="flex-col items-start">
55+
{imageList?.length ? (
56+
<div
57+
className={`grid gap-2 w-full ${
58+
imageList.length === 1
59+
? "grid-cols-1"
60+
: imageList.length === 2
61+
? "grid-cols-2"
62+
: imageList.length === 3
63+
? "grid-cols-3"
64+
: "grid-cols-2 md:grid-cols-4"
65+
}`}
66+
>
67+
{imageList.map((image) => (
68+
<div
69+
key={image}
70+
className="relative aspect-square overflow-hidden rounded-lg"
71+
>
72+
{/* eslint-disable-next-line @next/next/no-img-element */}
73+
<img
74+
src={image}
75+
alt="Moment image"
76+
className="object-cover w-full h-full hover:scale-105 transition-transform duration-200"
77+
/>
10278
</div>
10379
))}
104-
{/* image list */}
105-
{/* link card list */}
106-
</CardFooter>
107-
</Card>
108-
)
109-
)}
80+
</div>
81+
) : null}
82+
</CardFooter>
83+
</Card>
84+
))}
11085
</div>
11186
</main>
11287
);

0 commit comments

Comments
 (0)