Skip to content

Commit 0a84933

Browse files
committed
feat: add post detail
1 parent 87d08c3 commit 0a84933

File tree

6 files changed

+304
-56
lines changed

6 files changed

+304
-56
lines changed

core/server/mod.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,19 @@ if (root) fetchProps(root);
399399
const url = new URL(req.url);
400400
const key = url.pathname;
401401
let page: Page = this.#routePage[key];
402-
if (!page) return [];
403402
let params: Record<string, string | undefined> | undefined = undefined;
404-
const res = this.#getParamsPage(req, this.#routePage);
405-
if (res) {
406-
const [pg, prm] = res;
407-
page = pg;
408-
params = prm;
403+
404+
if (!page) {
405+
const res = this.#getParamsPage(req, this.#routePage);
406+
if (res) {
407+
const [pg, prm] = res;
408+
page = pg;
409+
params = prm;
410+
}
409411
}
412+
413+
if (!page) return [];
414+
410415
const ctx = this.serverOptions as Context;
411416
ctx.info = info;
412417
ctx.next = () => {};
@@ -417,8 +422,6 @@ if (root) fetchProps(root);
417422
ctx.stores = this.stores;
418423
ctx.render = async <T>(data: T, headers?: Headers) => {
419424
const r = new Render(this);
420-
// key = key === "/" ? "" : key;
421-
// key = url.origin + "/__/props" + key;
422425
return await r.render(key, page, data, this.getNonce(), headers);
423426
};
424427
ctx.send = <T>(data: T, status = 200, headers?: Headers) => {
@@ -515,11 +518,6 @@ if (root) fetchProps(root);
515518
) => {
516519
const ctx = options as Context;
517520
const _r = new Render(this);
518-
// if (!page) {
519-
// ctx.render = <T>(jsx: T) => {
520-
// return r.renderJsx(jsx as JSX.Element);
521-
// };
522-
// }
523521
ctx.send = <T>(data: T, status = 200) => {
524522
return createResponse(data, status);
525523
};

modules/home/home.page.tsx

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useState } from "preact/hooks";
1+
// deno-lint-ignore-file
2+
import { useEffect, useState } from "preact/hooks";
23
import { PageProps } from "@app/mod.ts";
34
import Header from "./header.tsx";
45
import { JSX } from "preact/jsx-runtime";
@@ -25,6 +26,18 @@ export default function Home({ data }: PageProps<{
2526
const [isSubmitting, setIsSubmitting] = useState(false);
2627
const [submitSuccess, setSubmitSuccess] = useState(false);
2728
const [isDark, setIsDark] = useState(true);
29+
const [isMobile, setIsMobile] = useState(false);
30+
31+
// Detect mobile devices
32+
useEffect(() => {
33+
const checkMobile = () => {
34+
setIsMobile(window.innerWidth < 768);
35+
};
36+
37+
checkMobile();
38+
window.addEventListener("resize", checkMobile);
39+
return () => window.removeEventListener("resize", checkMobile);
40+
}, []);
2841

2942
// Handle post submission
3043
const handleSubmit = async (e: JSX.TargetedEvent<HTMLFormElement, Event>) => {
@@ -94,11 +107,9 @@ export default function Home({ data }: PageProps<{
94107
setIsDark(!isDark);
95108
};
96109

97-
// Theme styles with solid colors
110+
// Theme styles with performance optimizations
98111
const themeStyles = {
99-
background: isDark
100-
? "#0f172a" /* Slate 900 - solid dark background */
101-
: "#f8fafc", /* Very light gray - solid light background */
112+
background: isDark ? "#0f172a" : "#f8fafc",
102113
cardBg: isDark ? "bg-gray-800/90" : "bg-white/90",
103114
text: isDark ? "text-gray-100" : "text-gray-800",
104115
input: isDark
@@ -112,40 +123,45 @@ export default function Home({ data }: PageProps<{
112123
? "text-purple-400 hover:text-purple-300"
113124
: "text-purple-600 hover:text-purple-500",
114125
cardBorder: isDark ? "border-gray-700" : "border-gray-200",
115-
cardGlow: isDark
116-
? "shadow-[0_0_35px_rgba(147,51,234,0.3)]" /* Enhanced purple glow for dark theme */
117-
: "shadow-[0_0_20px_rgba(147,51,234,0.15)]", /* Light purple glow */
126+
// Simplified shadows for mobile
127+
cardGlow: isMobile
128+
? isDark ? "shadow-md" : "shadow-sm"
129+
: isDark
130+
? "shadow-[0_0_35px_rgba(147,51,234,0.3)]"
131+
: "shadow-[0_0_20px_rgba(147,51,234,0.15)]",
118132
};
119133

120134
return (
121135
<div className="relative min-h-screen">
122-
{/* Background Layer */}
136+
{/* Background Layer - simplified for mobile */}
123137
<div className="fixed inset-0 z-0">
124138
{/* Solid Background */}
125139
<div
126140
className="absolute inset-0"
127141
style={{ backgroundColor: themeStyles.background }}
128142
/>
129143

130-
{/* Subtle Dot Pattern (optional - much simpler than gradient) */}
131-
<div className="absolute inset-0 z-[1]">
132-
<div
133-
className="absolute inset-0"
134-
style={{
135-
backgroundImage: `
136-
radial-gradient(${
137-
isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.03)"
138-
} 1px, transparent 1px)
139-
`,
140-
backgroundSize: "20px 20px",
141-
}}
142-
/>
143-
</div>
144+
{/* Dot pattern only on non-mobile */}
145+
{!isMobile && (
146+
<div className="absolute inset-0 z-[1]">
147+
<div
148+
className="absolute inset-0"
149+
style={{
150+
backgroundImage: `
151+
radial-gradient(${
152+
isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.03)"
153+
} 1px, transparent 1px)
154+
`,
155+
backgroundSize: "20px 20px",
156+
}}
157+
/>
158+
</div>
159+
)}
144160
</div>
145161

146162
{/* Content Layer */}
147163
<div className="relative z-10 min-h-screen">
148-
{/* Theme toggle button - moved to bottom-right corner */}
164+
{/* Theme toggle button */}
149165
<button
150166
type="button"
151167
onClick={toggleTheme}
@@ -159,7 +175,7 @@ export default function Home({ data }: PageProps<{
159175
{isDark ? "☀️" : "🌙"}
160176
</button>
161177

162-
<div className="max-w-xl mx-auto backdrop-blur-lg">
178+
<div className="max-w-xl mx-auto">
163179
<Header
164180
isLogin={data.isLogin}
165181
avatar_url={data.avatar_url}
@@ -168,14 +184,12 @@ export default function Home({ data }: PageProps<{
168184
/>
169185

170186
<main className="max-w-2xl mx-auto px-4">
171-
{/* Post creation card */}
187+
{/* Post creation card - reduced blur effects */}
172188
<div
173-
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 mb-4 border ${themeStyles.cardBorder} backdrop-blur-lg`}
189+
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 mb-4 border ${themeStyles.cardBorder} ${
190+
!isMobile ? "backdrop-blur-lg" : ""
191+
}`}
174192
>
175-
<h2 className={`text-2xl font-bold mb-4 ${themeStyles.text}`}>
176-
Create a Post
177-
</h2>
178-
179193
<form onSubmit={handleSubmit} className="space-y-4">
180194
<div>
181195
<textarea
@@ -189,9 +203,6 @@ export default function Home({ data }: PageProps<{
189203
</div>
190204

191205
<div className="flex justify-between items-center">
192-
{
193-
/* */
194-
}
195206
<div className="flex justify-start w-full">
196207
{submitSuccess && (
197208
<div className="min-w-10 bg-green-500/20 text-green-500 px-4 py-2 rounded-lg mr-2">
@@ -211,14 +222,20 @@ export default function Home({ data }: PageProps<{
211222
</form>
212223
</div>
213224

214-
{/* Posts list */}
225+
{/* Posts list - performance optimized */}
215226
<div className="space-y-4 mb-8">
216227
{posts.length > 0
217228
? (
218229
posts.map((post) => (
219230
<div
220231
key={post.id}
221-
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 border ${themeStyles.cardBorder} relative backdrop-blur-lg transition-all duration-300 hover:scale-[1.01]`}
232+
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 border ${themeStyles.cardBorder} relative ${
233+
!isMobile ? "backdrop-blur-lg" : ""
234+
} ${
235+
!isMobile
236+
? "transition-all duration-300 hover:scale-[1.01]"
237+
: ""
238+
}`}
222239
>
223240
{/* Delete button */}
224241
<button
@@ -238,9 +255,9 @@ export default function Home({ data }: PageProps<{
238255
viewBox="0 0 24 24"
239256
fill="none"
240257
stroke="currentColor"
241-
stroke-width="2"
242-
stroke-linecap="round"
243-
stroke-linejoin="round"
258+
strokeWidth="2"
259+
strokeLinecap="round"
260+
strokeLinejoin="round"
244261
>
245262
<path d="M3 6h18"></path>
246263
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6">
@@ -262,15 +279,23 @@ export default function Home({ data }: PageProps<{
262279
</p>
263280
</div>
264281
</div>
265-
<p className={`${themeStyles.text} whitespace-pre-wrap`}>
266-
{post.content}
267-
</p>
282+
283+
{/* Make the content clickable to view details */}
284+
<a href={`/post/${post.id}`} className="block">
285+
<p
286+
className={`${themeStyles.text} whitespace-pre-wrap`}
287+
>
288+
{post.content}
289+
</p>
290+
</a>
268291
</div>
269292
))
270293
)
271294
: (
272295
<div
273-
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 border ${themeStyles.cardBorder} text-center ${themeStyles.text} backdrop-blur-lg`}
296+
className={`${themeStyles.cardBg} rounded-lg ${themeStyles.cardGlow} p-6 border ${themeStyles.cardBorder} text-center ${themeStyles.text} ${
297+
!isMobile ? "backdrop-blur-lg" : ""
298+
}`}
274299
>
275300
<p>No posts yet. Be the first to post something!</p>
276301
</div>

modules/home/home.service.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ export async function getPosts(limit = 20): Promise<Post[]> {
5555
return posts;
5656
}
5757

58+
export async function getPostById(id: string): Promise<Post | null> {
59+
console.log("Fetching post with ID:", id);
60+
61+
try {
62+
// Use the correct key structure for your KV database
63+
const primaryKey = ["posts", id];
64+
65+
const result = await kv.get<Post>(primaryKey);
66+
if (!result.value) {
67+
console.log("Post not found with ID:", id);
68+
return null;
69+
}
70+
71+
return result.value;
72+
} catch (error) {
73+
console.error("Error fetching post:", error);
74+
return null;
75+
}
76+
}
77+
5878
export async function deletePostById(id: string): Promise<boolean> {
5979
const primaryKey = ["posts", id];
6080
console.log("Deleting post with ID:", id);

modules/home/mod.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import pageHandler, {
55
deletePostHandler,
66
postHandler,
77
} from "@app/modules/home/home.handler.ts";
8+
import postDetailComponent from "@app/modules/home/post.page.tsx";
9+
import postDetailHandler from "@app/modules/home/post.handler.ts";
810

911
export default function (s: Fastro) {
12+
console.log("Registering routes in home module");
13+
1014
// add page
1115
s.page("/home", {
1216
folder: "modules/home",
@@ -15,11 +19,23 @@ export default function (s: Fastro) {
1519
handler: pageHandler,
1620
});
1721

22+
// Add post detail page
23+
s.page("/post/:id", {
24+
folder: "modules/home",
25+
component: postDetailComponent,
26+
layout: pageLayout,
27+
handler: postDetailHandler,
28+
});
29+
1830
// Add API endpoint for post creation
1931
s.post("/api/post", postHandler);
2032

2133
// Add API endpoint for deleting a post
2234
s.delete("/api/post/:id", deletePostHandler);
2335

36+
// Log registered routes after registration
37+
console.log("Registered pages:", Object.keys(s.getPages()));
38+
console.log("Registered routes:", Object.keys(s.getRoutes()));
39+
2440
return s;
2541
}

modules/home/post.handler.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Context, HttpRequest } from "@app/mod.ts";
2+
import { getSession } from "@app/utils/session.ts";
3+
import { getPostById } from "@app/modules/home/home.service.ts";
4+
5+
export default async function postDetailHandler(
6+
req: HttpRequest,
7+
ctx: Context,
8+
) {
9+
// Extract post ID from URL parameters provided by Fastro
10+
const id = req.params?.id;
11+
12+
if (!id) {
13+
console.error("No post ID found in parameters");
14+
return new Response("No post ID provided", { status: 400 });
15+
}
16+
17+
// Check if user is logged in, but don't restrict access
18+
const ses = await getSession(req, ctx);
19+
const isLogin = ses?.isLogin || false;
20+
const avatar_url = ses?.avatar_url || "";
21+
const html_url = ses?.html_url || "";
22+
23+
// Get the post details
24+
const post = await getPostById(id);
25+
console.log("Post retrieved:", post ? "Yes" : "No");
26+
27+
if (!post) {
28+
// If post doesn't exist, redirect to home
29+
console.log("Post not found, redirecting to home");
30+
return new Response(null, {
31+
status: 302,
32+
headers: {
33+
Location: "/home",
34+
},
35+
});
36+
}
37+
38+
return await ctx.render({
39+
title: `Post by ${post.author}`,
40+
description: post.content.substring(0, 150) +
41+
(post.content.length > 150 ? "..." : ""),
42+
image: "https://fastro.deno.dev/fastro.png",
43+
isLogin,
44+
avatar_url,
45+
html_url,
46+
post,
47+
});
48+
}

0 commit comments

Comments
 (0)