Skip to content

Commit 7f1503d

Browse files
committed
feat: add home
1 parent b16fa8a commit 7f1503d

File tree

11 files changed

+671
-11
lines changed

11 files changed

+671
-11
lines changed

components/header.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import BoltSvg from "@app/components/icons/bolt.tsx";
2-
import AngleLeftSvg from "@app/components/icons/angle-left-svg.tsx";
2+
// import AngleLeftSvg from "@app/components/icons/angle-left-svg.tsx";
33
import GithubSvg from "@app/components/icons/github-svg.tsx";
4-
import RocketSvg from "./icons/rocket-svg.tsx";
4+
// import RocketSvg from "./icons/rocket-svg.tsx";
55

66
export default function Header(
77
props: {
@@ -25,11 +25,7 @@ export default function Header(
2525
<div
2626
class={`border-[1px] border-gray-600 bg-gray-900 rounded-full p-[1px]`}
2727
>
28-
{props.isLogin
29-
? <RocketSvg />
30-
: props.previous_url
31-
? <AngleLeftSvg />
32-
: <BoltSvg />}
28+
<BoltSvg />
3329
</div>
3430
</a>
3531
<span class={`${textColorClass}`}>

modules/app/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import docsLayout from "@app/modules/docs/docs.layout.tsx";
77
import blog from "@app/modules/blog/mod.ts";
88
import docs from "@app/modules/docs/mod.ts";
99
import authModule from "@app/modules/auth/mod.tsx";
10+
import homeModule from "@app/modules/home/mod.ts";
1011

1112
const s = new Server();
1213

@@ -19,5 +20,6 @@ s.group(waitModule);
1920
s.group(blog);
2021
s.group(docs);
2122
s.group(authModule);
23+
s.group(homeModule);
2224

2325
await s.serve();

modules/auth/mod.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ export const signoutHandler = async (req: HttpRequest, ctx: Context) => {
133133
store.delete(sessionId);
134134
await store.commit();
135135
}
136-
return await signOut(req);
136+
await signOut(req);
137+
return new Response(null, {
138+
status: 302,
139+
headers: {
140+
Location: "/",
141+
},
142+
});
137143
}
138144
};
139145

modules/home/header.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import BoltSvg from "@app/components/icons/bolt.tsx";
2+
// import AngleLeftSvg from "@app/components/icons/angle-left-svg.tsx";
3+
import GithubSvg from "@app/components/icons/github-svg.tsx";
4+
// import RocketSvg from "./icons/rocket-svg.tsx";
5+
6+
export default function Header(
7+
props: {
8+
isLogin: boolean;
9+
avatar_url: string;
10+
html_url: string;
11+
title?: string;
12+
previous_url?: string;
13+
isDark?: boolean;
14+
},
15+
) {
16+
const textColorClass = props.isDark ? "text-gray-100" : "text-gray-700";
17+
const linkTextColorClass = props.isDark ? "text-gray-100" : "text-gray-700"; // Define link text color
18+
19+
return (
20+
<div
21+
class={`container flex justify-between max-w-4xl mx-auto text-center text-sm py-6 px-3 xl:px-0 md:px-0 sm:px-0 ${textColorClass}`}
22+
>
23+
<div class={`flex space-x-2 items-center`}>
24+
<a href="/" class={`text-gray-100`}>
25+
<div
26+
class={`border-[1px] border-gray-600 bg-gray-900 rounded-full p-[1px]`}
27+
>
28+
<BoltSvg />
29+
</div>
30+
</a>
31+
<span class={`${textColorClass}`}>
32+
{`${props.title || "Fastro"}`}
33+
</span>
34+
</div>
35+
<div class={`flex items-center space-x-3`}>
36+
{props.isLogin && (
37+
<a class={`${linkTextColorClass}`} href="/signout">Sign out</a>
38+
)}
39+
{!props.isLogin && (
40+
<a class={`${linkTextColorClass}`} href="/signin">Sign in</a>
41+
)}
42+
43+
<a
44+
aria-label="user profile"
45+
class={`${linkTextColorClass}`}
46+
href={props.isLogin
47+
? props.html_url
48+
: "https://github.com/fastrodev/fastro"}
49+
>
50+
{!props.avatar_url ? <GithubSvg /> : (
51+
<img
52+
loading="lazy"
53+
src={props.avatar_url}
54+
width={24}
55+
class={`rounded-full`}
56+
/>
57+
)}
58+
</a>
59+
</div>
60+
</div>
61+
);
62+
}

modules/home/home.handler.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Context, HttpRequest } from "@app/mod.ts";
2+
import { getSession } from "@app/utils/session.ts";
3+
import {
4+
createPost,
5+
deletePostById,
6+
getPosts,
7+
} from "@app/modules/home/home.service.ts";
8+
9+
export default async function homeHandler(req: HttpRequest, ctx: Context) {
10+
const ses = await getSession(req, ctx);
11+
const isLogin = ses?.isLogin;
12+
if (!isLogin) {
13+
return new Response(null, {
14+
status: 302,
15+
headers: {
16+
Location: "/",
17+
},
18+
});
19+
}
20+
21+
const avatar_url = ses?.avatar_url;
22+
const html_url = ses?.html_url;
23+
// Get posts for the initial page load
24+
const posts = await getPosts();
25+
console.log("Posts data:", JSON.stringify(posts, null, 2));
26+
console.log("Number of posts:", posts.length);
27+
28+
return await ctx.render({
29+
title: "Home",
30+
description: "Share your thoughts and connect with others",
31+
image: "https://fastro.deno.dev/fastro.png",
32+
isLogin,
33+
avatar_url,
34+
html_url,
35+
posts,
36+
});
37+
}
38+
39+
export async function postHandler(req: HttpRequest, ctx: Context) {
40+
try {
41+
const body = await req.json();
42+
const content = body.content;
43+
44+
if (!content || typeof content !== "string" || content.trim() === "") {
45+
return new Response(JSON.stringify({ error: "Content is required" }), {
46+
status: 400,
47+
headers: { "Content-Type": "application/json" },
48+
});
49+
}
50+
51+
// Get the user from session
52+
const ses = await getSession(req, ctx);
53+
const username = ses?.username;
54+
55+
// Create the post
56+
const post = await createPost({
57+
content,
58+
author: username,
59+
});
60+
61+
return new Response(JSON.stringify(post), {
62+
status: 201,
63+
headers: {
64+
"Content-Type": "application/json",
65+
},
66+
});
67+
} catch (error) {
68+
console.error("Error processing post request:", error);
69+
return new Response(JSON.stringify({ error: "Invalid request" }), {
70+
status: 400,
71+
headers: { "Content-Type": "application/json" },
72+
});
73+
}
74+
}
75+
76+
export async function deletePostHandler(req: HttpRequest, ctx: Context) {
77+
try {
78+
// Extract post ID from the URL path instead of query parameters
79+
const url = new URL(req.url);
80+
const pathParts = url.pathname.split("/");
81+
const id = pathParts[pathParts.length - 1]; // Get the last part of the path
82+
83+
if (!id) {
84+
return new Response(JSON.stringify({ error: "Post ID is required" }), {
85+
status: 400,
86+
headers: { "Content-Type": "application/json" },
87+
});
88+
}
89+
90+
// Get the user from session for authorization
91+
const ses = await getSession(req, ctx);
92+
if (!ses?.isLogin) {
93+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
94+
status: 401,
95+
headers: { "Content-Type": "application/json" },
96+
});
97+
}
98+
99+
// Delete the post
100+
const success = await deletePostById(id);
101+
102+
if (!success) {
103+
return new Response(JSON.stringify({ error: "Post not found" }), {
104+
status: 404,
105+
headers: { "Content-Type": "application/json" },
106+
});
107+
}
108+
109+
return new Response(JSON.stringify({ success: true }), {
110+
status: 200,
111+
headers: { "Content-Type": "application/json" },
112+
});
113+
} catch (error) {
114+
console.error("Error processing delete request:", error);
115+
return new Response(JSON.stringify({ error: "Server error" }), {
116+
status: 500,
117+
headers: { "Content-Type": "application/json" },
118+
});
119+
}
120+
}

modules/home/home.layout.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { LayoutProps } from "@app/core/server/types.ts";
2+
3+
export default function layout(
4+
{ data, children }: LayoutProps<
5+
{ title: string; description: string; image: string }
6+
>,
7+
) {
8+
return (
9+
<html lang="en">
10+
<head>
11+
<meta charset="utf-8" />
12+
<meta
13+
name="viewport"
14+
content="width=device-width, initial-scale=1, viewport-fit=cover"
15+
/>
16+
<title>{data.title} | Fastro</title>
17+
18+
{/* Primary Meta Tags */}
19+
<meta
20+
name="title"
21+
content={`${data.title} | Fastro`}
22+
/>
23+
<meta name="description" content={data.description} />
24+
<meta
25+
name="keywords"
26+
content="fastro, post, social media, content sharing"
27+
/>
28+
29+
{/* Open Graph / Facebook */}
30+
<meta property="og:type" content="website" />
31+
<meta property="og:url" content="https://fastro.dev" />
32+
<meta
33+
property="og:title"
34+
content={`${data.title} | Fastro`}
35+
/>
36+
<meta property="og:description" content={data.description} />
37+
<meta property="og:image" content={data.image} />
38+
<meta property="og:site_name" content="Fastro" />
39+
40+
{/* Twitter */}
41+
<meta property="twitter:card" content="summary_large_image" />
42+
<meta property="twitter:url" content="https://fastro.dev" />
43+
<meta
44+
property="twitter:title"
45+
content={`${data.title} | Fastro`}
46+
/>
47+
<meta property="twitter:description" content={data.description} />
48+
<meta property="twitter:image" content={data.image} />
49+
50+
{/* PWA Tags */}
51+
<meta name="mobile-web-app-capable" content="yes" />
52+
<meta name="apple-mobile-web-app-capable" content="yes" />
53+
<meta
54+
name="apple-mobile-web-app-status-bar-style"
55+
content="black-translucent"
56+
/>
57+
<meta name="theme-color" content="#000000" />
58+
59+
{/* Links */}
60+
<link href="/styles.css" rel="stylesheet" />
61+
<link rel="manifest" href="/manifest.json" />
62+
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
63+
<link rel="canonical" href="https://fastro.dev" />
64+
</head>
65+
<body id="root">
66+
{children}
67+
</body>
68+
</html>
69+
);
70+
}

0 commit comments

Comments
 (0)