got frontend working with backend project
This commit is contained in:
parent
66ba89c0b1
commit
0093589a4b
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
22
frontend/islands/PostCard.tsx
Normal file
22
frontend/islands/PostCard.tsx
Normal 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;
|
||||||
|
}
|
21
frontend/islands/PostCarousel.tsx
Normal file
21
frontend/islands/PostCarousel.tsx
Normal 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
3
frontend/lib/truncate.ts
Normal 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
57
frontend/lib/useFetch.tsx
Normal 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;
|
||||||
|
}
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
0
frontend/routes/authors/[id].tsx
Normal file
0
frontend/routes/authors/[id].tsx
Normal file
0
frontend/routes/authors/index.tsx
Normal file
0
frontend/routes/authors/index.tsx
Normal 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>;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user