got frontend working with backend project

This commit is contained in:
Wyatt J. Miller 2024-11-27 00:35:32 -05:00
parent 66ba89c0b1
commit 0093589a4b
11 changed files with 250 additions and 63 deletions

View File

@ -1,3 +1,41 @@
export default function Header() { interface HeaderLink {
return <div>THIS IS THE HEADER</div>; name: string;
linkTo: string;
newTab?: boolean;
}
const headerLinks: Array<HeaderLink> = [
{
name: "Home",
linkTo: "/",
},
{
name: "Blog",
linkTo: "posts/",
},
{
name: "Projects",
linkTo: "projects",
},
{
name: "Contact",
linkTo: "contact/",
},
];
export default function Header() {
return (
<div>
{headerLinks.map((l) => {
const newTab = l.newTab ? "_blank" : "_self";
return (
<div class="">
<a href={l.linkTo} target={newTab} class="">
{l.name}
</a>
</div>
);
})}
</div>
);
} }

View File

@ -5,10 +5,14 @@
import * as $_404 from "./routes/_404.tsx"; import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx"; import * as $_app from "./routes/_app.tsx";
import * as $_layout from "./routes/_layout.tsx"; import * as $_layout from "./routes/_layout.tsx";
import * as $authors_id_ from "./routes/authors/[id].tsx";
import * as $authors_index from "./routes/authors/index.tsx";
import * as $index from "./routes/index.tsx"; import * as $index from "./routes/index.tsx";
import * as $posts_id_ from "./routes/posts/[id].tsx"; import * as $posts_id_ from "./routes/posts/[id].tsx";
import * as $posts_index from "./routes/posts/index.tsx"; import * as $posts_index from "./routes/posts/index.tsx";
import * as $Counter from "./islands/Counter.tsx"; import * as $Counter from "./islands/Counter.tsx";
import * as $PostCard from "./islands/PostCard.tsx";
import * as $PostCarousel from "./islands/PostCarousel.tsx";
import { type Manifest } from "$fresh/server.ts"; import { type Manifest } from "$fresh/server.ts";
const manifest = { const manifest = {
@ -16,12 +20,16 @@ const manifest = {
"./routes/_404.tsx": $_404, "./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app, "./routes/_app.tsx": $_app,
"./routes/_layout.tsx": $_layout, "./routes/_layout.tsx": $_layout,
"./routes/authors/[id].tsx": $authors_id_,
"./routes/authors/index.tsx": $authors_index,
"./routes/index.tsx": $index, "./routes/index.tsx": $index,
"./routes/posts/[id].tsx": $posts_id_, "./routes/posts/[id].tsx": $posts_id_,
"./routes/posts/index.tsx": $posts_index, "./routes/posts/index.tsx": $posts_index,
}, },
islands: { islands: {
"./islands/Counter.tsx": $Counter, "./islands/Counter.tsx": $Counter,
"./islands/PostCard.tsx": $PostCard,
"./islands/PostCarousel.tsx": $PostCarousel,
}, },
baseUrl: import.meta.url, baseUrl: import.meta.url,
} satisfies Manifest; } satisfies Manifest;

View File

@ -0,0 +1,22 @@
import { truncateString } from "../lib/truncate.ts";
export const PostCard = function PostCard({ post }: { post: Post }) {
return (
<div class="p-6 bg-gray-700 rounded-lg shadow-md">
<h2 class="text-grey-900 text-lg font-bold mb-2">{post.title}</h2>
<p>
Written by {post.first_name} {post.last_name} at {post.created_at}
</p>
<p>{truncateString(post.body, 15)}</p>
</div>
);
};
export interface Post {
post_id: number;
first_name: string;
last_name: string;
title: string;
body: string;
created_at: string;
}

View File

@ -0,0 +1,21 @@
import { Post, PostCard } from "./PostCard.tsx";
interface PostOpts {
posts: Post[];
}
export const PostCarousel = function PostCarousel({ posts }: PostOpts) {
return (
<div class="post-carousel">
<div class="h-screen bg-gray-700 flex items-center justify-center">
<div class="flex bg-gray-700 space-x-4">
{posts.map((post: Post, idx: number) => {
if (idx < 3) {
return <PostCard post={post} />;
}
})}
</div>
</div>
</div>
);
};

3
frontend/lib/truncate.ts Normal file
View File

@ -0,0 +1,3 @@
export const truncateString = (str: string, maxLength: number) => {
return str.length > maxLength ? `${str.slice(0, maxLength)}...` : str;
};

57
frontend/lib/useFetch.tsx Normal file
View File

@ -0,0 +1,57 @@
import { useEffect } from "preact/hooks";
import { signal } from "@preact/signals";
export function useFetch<T>(
url: string,
options?: FetchOptions,
): {
data: T | null;
loading: boolean;
error: Error | null;
refetch: (newURL?: string, newOptions?: FetchOptions) => void;
} {
const data = signal<T | null>(null);
const loading = signal<boolean>(true);
const error = signal<Error | null>(null);
useEffect(() => {
fetch(url, options)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
})
.then((resp) => (data.value = resp))
.catch((err) => (error.value = err))
.finally(() => (loading.value = false));
}, [url, options]);
const refetch = (newURL?: string, newOptions?: FetchOptions) => {
loading.value = true;
error.value = null;
fetch(newURL || url, newOptions || options)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
})
.then((resp) => (data.value = resp))
.catch((err) => (error.value = err))
.finally(() => (loading.value = false));
};
return {
data: data.value,
loading: loading.value,
error: error.value,
refetch,
};
}
export interface FetchOptions {
method: "GET" | "POST" | "PUT" | "DELETE";
headers: Record<string, string>;
body: string;
}

View File

@ -8,18 +8,11 @@ export default function Error404() {
</Head> </Head>
<div class="px-4 py-8 mx-auto bg-[#86efac]"> <div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center"> <div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">404 - Page not found</h1> <h1 class="text-4xl font-bold">404 - Page not found</h1>
<p class="my-4"> <p class="my-4">The page you were looking for doesn't exist.</p>
The page you were looking for doesn't exist. <a href="/" class="underline">
</p> Go back home
<a href="/" class="underline">Go back home</a> </a>
</div> </div>
</div> </div>
</> </>

View File

View File

View File

@ -1,3 +1,16 @@
export default function PostIdentifier() { import { FreshContext, Handlers, PageProps } from "$fresh/server.ts";
return null; import { useFetch } from "../../lib/useFetch.tsx";
interface PostResponse {
post_id: number;
first_nane: string;
last_name: string;
title: string;
body: string;
created_at: string;
}
export default function PostIdentifier(props: PageProps) {
console.log(props.data);
return <div>BLOG POST #{props.params.id}</div>;
} }

View File

@ -1,52 +1,84 @@
export default function Posts() { // import { FreshContext, Handlers, PageProps } from "$fresh/server.ts";
import { PostCarousel } from "../../islands/PostCarousel.tsx";
// export interface PostItems {
// title: string;
// author: string;
// publish_date: string;
// truncated_content: string;
// }
//
// export interface PostReponse {
// new_posts: Array<PostItems>;
// popular_posts: Array<PostItems>;
// hot_posts: Array<PostItems>;
// }
//
// export interface PostResponseOne {
// post_id: number;
// first_name: string;
// last_name: string;
// title: string;
// body: string;
// created_at: string;
// }
//
// export const handler: Handlers<Array<PostCard>> = {
// GET(_req: Request, ctx: FreshContext) {
// const results = fetch(`${Deno.env.get("BASE_URI")}/posts/all`);
// return ctx.render(results.then((resp) => resp.json()));
// },
// };
import { Handlers, PageProps } from "$fresh/server.ts";
import { Post } from "../../islands/PostCard.tsx";
interface PageData {
featuredPosts: Post[];
hotPosts: Post[];
popularPosts: Post[];
}
export const handler: Handlers<PageData> = {
async GET(_: any, ctx: any) {
const [featuredResult, hotResult, popularResult] = await Promise.all([
fetch(`${Deno.env.get("BASE_URI")}/posts/all`),
fetch(`${Deno.env.get("BASE_URI")}/posts/hot`),
fetch(`${Deno.env.get("BASE_URI")}/posts/popular`),
]);
// Parse all JSON responses concurrently
const [featuredPosts, hotPosts, popularPosts] = await Promise.all([
featuredResult.json(),
hotResult.json(),
popularResult.json(),
]);
return ctx.render({
featuredPosts,
hotPosts,
popularPosts,
});
},
};
export default function PostPage({ data }: PageProps<PageData>) {
const { featuredPosts, hotPosts, popularPosts } = data;
return ( return (
<div class="min-w-screen flex flex-col items-center justify-between bg-gray-100 dark:bg-gray-700 sm:min-h-screen"> <div class="space-y-12 py-8">
<div class="space-y-2 text-center md:text-left"> <section>
<h1 class="text-2xl text-white font-bold sm:text-4xl">Blog posts</h1> <h2 class="text-2xl font-bold mb-4">Featured Posts</h2>
<h2 class="text-md font-medium text-cyan-700 dark:text-cyan-200 sm:text-xl"> <PostCarousel posts={featuredPosts} />
A lot of them! </section>
</h2> <section>
</div> <h2 class="text-2xl font-bold mb-4">Recent Posts</h2>
<div class="h-screen bg-gray-700 flex items-center justify-center"> <PostCarousel posts={hotPosts} />
<div class="flex bg-gray-700 space-x-4"> </section>
<div class="p-6 bg-gray-700 rounded-lg shadow-md"> <section>
<h2 class="text-grey-900 text-lg font-bold mb-2">Blog Post 1</h2> <h2 class="text-2xl font-bold mb-4">Popular Posts</h2>
<p>Written by Wyatt Miller</p> <PostCarousel posts={popularPosts} />
<p> </section>
Lorem ipsum odor amet, consectetuer adipiscing elit. Senectus
aliquet fusce habitant sem integer lectus curae quisque. Tincidunt
vitae adipiscing justo et nulla. Faucibus imperdiet turpis maximus
nam natoque suscipit platea. Feugiat imperdiet malesuada enim diam
primis. Vitae sollicitudin molestie cubilia tempus nunc dignissim
adipiscing.
</p>
</div>
<div class="p-6 bg-gray-700 rounded-lg shadow-md">
<h2 class="text-grey-900 text-lg font-bold mb-2">Blog Post 2</h2>
<p>Written by Wyatt Miller</p>
<p>
Lorem ipsum odor amet, consectetuer adipiscing elit. Senectus
aliquet fusce habitant sem integer lectus curae quisque. Tincidunt
vitae adipiscing justo et nulla. Faucibus imperdiet turpis maximus
nam natoque suscipit platea. Feugiat imperdiet malesuada enim diam
primis. Vitae sollicitudin molestie cubilia tempus nunc dignissim
adipiscing.
</p>
</div>
<div class="p-6 bg-gray-700 rounded-lg shadow-md">
<h2 class="text-grey-900 text-lg font-bold mb-2">Blog Post 3</h2>
<p>Written by Wyatt Miller</p>
<p>
Lorem ipsum odor amet, consectetuer adipiscing elit. Senectus
aliquet fusce habitant sem integer lectus curae quisque. Tincidunt
vitae adipiscing justo et nulla. Faucibus imperdiet turpis maximus
nam natoque suscipit platea. Feugiat imperdiet malesuada enim diam
primis. Vitae sollicitudin molestie cubilia tempus nunc dignissim
adipiscing.
</p>
</div>
</div>
</div>
</div> </div>
); );
} }