code dump frontend

This commit is contained in:
2024-12-02 18:29:45 -05:00
parent 44f1f35caa
commit 9e8cf4b147
19 changed files with 681 additions and 184 deletions

View File

@ -0,0 +1,73 @@
import { FreshContext, Handlers, PageProps } from "$fresh/server.ts";
import AuthorCard from "../../components/AuthorCard.tsx";
import { Post } from "../../components/PostCard.tsx";
import { PostCarousel } from "../../components/PostCarousel.tsx";
export const handler: Handlers<PageData> = {
async GET(_req: Request, ctx: FreshContext) {
try {
const [authorResponse, authorPostResponse] = await Promise.all([
fetch(`${Deno.env.get("BASE_URI_API")}/authors/${ctx.params.id}`),
fetch(`${Deno.env.get("BASE_URI_API")}/authors/${ctx.params.id}/posts`),
]);
const [authorData, authorPostData] = await Promise.all([
authorResponse.json(),
authorPostResponse.json(),
]);
return ctx.render({
authorData,
authorPostData,
});
} catch (error) {
return ctx.render({
error: error.message,
authorData: null,
authorPostData: [],
});
}
},
};
export default function AuthorIdentifier({ data }: PageProps<PageData>) {
const { authorData, authorPostData, error } = data;
if (error) {
return (
<div>
<h1>Error Loading Author Information</h1>
<p>{error}</p>
</div>
);
}
if (!authorData) {
return <div>No author found</div>;
}
return (
<>
<div>
<AuthorCard author={authorData} isIdentified={true} />
</div>
<div>
<PostCarousel posts={authorPostData} />
</div>
</>
);
}
interface PageData {
error?: string;
authorData: AuthorResponse;
authorPostData: Array<Post>;
}
export type AuthorResponse = {
author_id: number;
first_name: string;
last_name: string;
bio: string;
image?: string;
};

View File

@ -0,0 +1,171 @@
import { Handlers, PageProps } from "$fresh/server.ts";
import { Head } from "$fresh/runtime.ts";
interface FormState {
name?: string;
email?: string;
message?: string;
errors?: {
name?: string;
email?: string;
message?: string;
};
submitted?: boolean;
}
export default function Contact({ data }: PageProps<FormState>) {
return (
<div class="max-w-md mx-auto p-6 bg-[#313244] rounded-lg shadow-md">
<Head>
<title>Contact Us</title>
</Head>
<h1 class="text-2xl font-bold mb-6 text-center">Contact Form</h1>
{/* Check if form was successfully submitted */}
{data?.submitted && (
<div
class="bg-[#a6e3a1] text-[#cdd6f4] px-4 py-3 rounded relative"
role="alert"
>
Your message has been sent successfully!
</div>
)}
<form method="POST" class="space-y-4">
{/* Name Input */}
<div>
<label
htmlFor="name"
class="block text-[#cdd6f4] text-sm font-bold mb-2"
>
Name
</label>
<input
type="text"
id="name"
name="name"
required
placeholder="Your Name"
value={data?.name || ""}
class={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500
${data?.errors?.name ? "border-red-500" : "border-gray-300"}`}
/>
{data?.errors?.name && (
<p class="text-red-500 text-xs italic mt-1">{data.errors.name}</p>
)}
</div>
{/* Email Input */}
<div>
<label
htmlFor="email"
class="block text-[#cdd6f4] text-sm font-bold mb-2"
>
Email
</label>
<input
type="email"
id="email"
name="email"
required
placeholder="your@email.com"
value={data?.email || ""}
class={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500
${data?.errors?.email ? "border-red-500" : "border-gray-300"}`}
/>
{data?.errors?.email && (
<p class="text-red-500 text-xs italic mt-1">{data.errors.email}</p>
)}
</div>
{/* Message Textarea */}
<div>
<label
htmlFor="message"
class="block text-[#cdd6f4] text-sm font-bold mb-2"
>
Message
</label>
<textarea
id="message"
name="message"
required
placeholder="Write your message here..."
rows={4}
class={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500
${data?.errors?.message ? "border-red-500" : "border-gray-300"}`}
>
{data?.message || ""}
</textarea>
{data?.errors?.message && (
<p class="text-red-500 text-xs italic mt-1">
{data.errors.message}
</p>
)}
</div>
{/* Submit Button */}
<div>
<button
type="submit"
class="w-full bg-[#89b4fa] text-[#cdd6f4] py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Send Message
</button>
</div>
</form>
</div>
);
}
// Server-side form handling
export const handler: Handlers = {
GET(_, ctx) {
return ctx.render({});
},
async POST(req, ctx) {
const formData = await req.formData();
const state: FormState = {
name: formData.get("name")?.toString(),
email: formData.get("email")?.toString(),
message: formData.get("message")?.toString(),
};
// Validation logic
const errors: FormState["errors"] = {};
if (!state.name || state.name.trim() === "") {
errors.name = "Name is required";
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!state.email) {
errors.email = "Email is required";
} else if (!emailRegex.test(state.email)) {
errors.email = "Invalid email format";
}
if (!state.message || state.message.trim() === "") {
errors.message = "Message is required";
}
// If there are errors, return the form with error messages
if (Object.keys(errors).length > 0) {
return ctx.render({
...state,
errors,
});
}
// TODO: Implement actual form submission logic here
// For example, send email, save to database, etc.
console.log("Form submitted:", state);
// Return successful submission
return ctx.render({
...state,
submitted: true,
});
},
};

View File

@ -1,25 +1,27 @@
import { PhotoCircle } from "../components/PhotoCircle.tsx";
export default function Home() {
return (
<body>
<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="min-w-screen flex flex-col items-center justify-between bg-[#313244] sm:min-h-screen">
<div class="sm:mt-14 sm:mb-14 mt-12 mb-4 flex flex-col items-center gap-y-5 gap-x-10 md:flex-row">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
<PhotoCircle
src="https://websites.us-east-1.linodeobjects.com/IMG_1480-min.png"
alt="Wyatt's profile photo"
/>
<div class="space-y-2 text-center md:text-left">
<h1 class="text-2xl text-white font-bold sm:text-4xl">
Heya! I'm Wyatt Miller
<h1 class="text-2xl text-[#f5e0dc] font-bold sm:text-4xl">
Heya! I'm Wyatt
</h1>
<h2 class="text-md font-medium text-cyan-700 dark:text-cyan-200 sm:text-xl">
<h2 class="text-md font-medium text-[#f2cdcd] sm:text-xl">
Thanks for checking out this corner of the Internet!
</h2>
<h3 class="text-sm font-light italic text-[#f5e0dc] sm:text-md">
I design and develop software for developers, for end users, and
everyone in between
</h3>
</div>
</div>
{/** about me stuff */}
</div>
</body>
);

View File

@ -1,16 +1,28 @@
import { FreshContext, Handlers, PageProps } from "$fresh/server.ts";
import { useFetch } from "../../lib/useFetch.tsx";
interface PostResponse {
interface PageData {
post_id: number;
first_nane: string;
last_name: string;
title: string;
body: string;
created_at: string;
is_featured: boolean;
}
export default function PostIdentifier(props: PageProps) {
console.log(props.data);
return <div>BLOG POST #{props.params.id}</div>;
export const handler: Handlers<PageData> = {
async GET(_req: Request, ctx: FreshContext) {
const postResult = await fetch(
`${Deno.env.get("BASE_URI_API")}/posts/${ctx.params.id}`,
);
const postData = await postResult.json();
return ctx.render({
postData,
});
},
};
export default function PostIdentifier({ data }: PageProps<PageData>) {
return <div className=""></div>;
}

View File

@ -1,61 +1,37 @@
// 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 { PostCarousel } from "../../components/PostCarousel.tsx";
import { Handlers, PageProps } from "$fresh/server.ts";
import { Post } from "../../islands/PostCard.tsx";
import { Post } from "../../components/PostCard.tsx";
interface PageData {
featuredPosts: Post[];
recentPosts: 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`),
]);
const [featuredResult, recentResult, hotResult, popularResult] =
await Promise.all([
fetch(`${Deno.env.get("BASE_URI_API")}/posts/featured`),
fetch(`${Deno.env.get("BASE_URI_API")}/posts/recent`),
fetch(`${Deno.env.get("BASE_URI_API")}/posts/hot`),
fetch(`${Deno.env.get("BASE_URI_API")}/posts/popular`),
]);
// Parse all JSON responses concurrently
const [featuredPosts, hotPosts, popularPosts] = await Promise.all([
featuredResult.json(),
hotResult.json(),
popularResult.json(),
]);
// parse all JSON responses concurrently
const [featuredPosts, recentPosts, hotPosts, popularPosts] = await Promise
.all([
featuredResult.json(),
recentResult.json(),
hotResult.json(),
popularResult.json(),
]);
return ctx.render({
featuredPosts,
recentPosts,
hotPosts,
popularPosts,
});
@ -63,20 +39,45 @@ export const handler: Handlers<PageData> = {
};
export default function PostPage({ data }: PageProps<PageData>) {
const { featuredPosts, hotPosts, popularPosts } = data;
const { featuredPosts, recentPosts, hotPosts, popularPosts } = data;
return (
<div class="space-y-12 py-8">
<div class="space-y-12 px-10 py-8 bg-[#313244]">
<h1 class="text-3xl text-white font-bold uppercase text-center">Blog</h1>
<section>
<h2 class="text-2xl font-bold mb-4">Featured Posts</h2>
<h2 class="text-2xl font-bold mb-2 text-white text-center lg:text-left">
Featured Posts
</h2>
<div className="text-lg font-thin italic mb-4 text-white text-center lg:text-left">
Ignite the impossible
</div>
<PostCarousel posts={featuredPosts} />
</section>
<section>
<h2 class="text-2xl font-bold mb-4">Recent Posts</h2>
<h2 class="text-2xl font-bold mb-2 text-white text-center lg:text-left">
Recent Posts
</h2>
<div className="text-lg font-thin italic mb-4 text-white text-center lg:text-left">
Now with 100% fresh perspective
</div>
<PostCarousel posts={recentPosts} />
</section>
<section>
<h2 class="text-2xl font-bold mb-2 text-white text-center lg:text-left">
Hot Posts
</h2>
<div className="text-lg font-thin italic mb-4 text-white text-center lg:text-left">
Making chaos look cool since forever
</div>
<PostCarousel posts={hotPosts} />
</section>
<section>
<h2 class="text-2xl font-bold mb-4">Popular Posts</h2>
<h2 class="text-2xl font-bold mb-2 text-white text-center lg:text-left">
Popular Posts
</h2>
<div className="text-lg font-thin italic mb-4 text-white text-center lg:text-left">
Content may cause uncontrollable reading
</div>
<PostCarousel posts={popularPosts} />
</section>
</div>

View File

@ -0,0 +1,67 @@
import { ProjectCard } from "../../islands/ProjectCard.tsx";
export default function Projects() {
return (
<div class="space-y-12 px-10 py-8 sm:min-h-screen bg-[#313244]">
<section
id="projects"
class="lg:grid-cols-desktop grid scroll-mt-16 grid-cols-1 gap-x-10 gap-y-4 bg-[#313244] "
>
<h1 class="text-3xl text-white font-bold uppercase text-center">
Projects
</h1>
<div class="grid grid-cols-1 sm:grid-cols-2 ">
<ProjectCard
wip
title="Website v2"
summary="This website was built by yours truly!"
// repo="https://scm.wyattjmiller.com/wymiller/my-website-v2"
tech="Typescript, Deno, Fresh, Tailwind, Rust, PostgreSQL, Docker"
/>
<ProjectCard
title="BallBot"
repo="https://scm.wyattjmiller.com/wymiller/ballbot"
summary="A Discord bot that tells me NFL games, teams, and more!"
tech="Rust, Discord SDK, Docker"
/>
<ProjectCard
title="Nix configurations"
repo="https://scm.wyattjmiller.com/wymiller/nix-config-v2"
summary="My 'master' declarative system configuration for multiple computers"
tech="Nix"
/>
<ProjectCard
wip
title="omega"
summary="Work-in-progress music bot for Discord that plays music from different music sources"
tech="Rust, Discord SDK, SurrealDB, yt-dlp"
/>
<ProjectCard
title="gt"
repo="https://scm.wyattjmiller.com/wymiller/gt"
summary="Command line application to interact with Gitea"
tech="Rust"
/>
<ProjectCard
title="The Boyos Bot"
repo="https://github.com/NoahFlowa/BoyosBot"
summary="All-in-one Discord bot, built with my friend, NoahFlowa"
tech="Javascript, Node, Discord SDK, Docker"
/>
<ProjectCard
title="drillsergeant"
repo="https://scm.wyattjmiller.com/wymiller/drillsergeant"
summary="Git commit counter, to scratch an itch I had"
tech="C#, .NET"
/>
<ProjectCard
title="bleak"
repo="https://scm.wyattjmiller.com/wymiller/bleak"
summary="Turns your Raspberry Pi into a lighting controller"
tech="Rust"
/>
</div>
</section>
</div>
);
}