mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 11:03:50 +00:00
Begin writing projects
This commit is contained in:
BIN
src/public/projects/reviveauto/thumbnail.jpeg
Normal file
BIN
src/public/projects/reviveauto/thumbnail.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -8,6 +8,7 @@ type BlogPost = {
|
||||
date: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
image?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,36 +26,70 @@ const formatDate = (dateString: string) => {
|
||||
|
||||
export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-4 py-8">
|
||||
<ul className="space-y-8">
|
||||
<div className="w-full max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl md:text-4xl font-bold mb-6 md:mb-10 text-yellow-bright px-4 md:px-0">Blog Posts</h1>
|
||||
<ul className="space-y-6 md:space-y-10">
|
||||
{posts.map((post) => (
|
||||
<li key={post.slug} className="border-b border-gray-200 pb-8 last:border-b-0">
|
||||
<li key={post.slug} className="group px-4 md:px-0">
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
className="text-xl font-semibold hover:text-blue-600 transition-colors duration-200"
|
||||
className="block"
|
||||
>
|
||||
{post.data.title}
|
||||
<article className="flex flex-col md:flex-row gap-4 md:gap-8 pb-6 md:pb-10 border-b border-foreground/20 last:border-b-0 p-2 md:p-4 rounded-lg group-hover:outline group-hover:outline-2 group-hover:outline-purple transition-all duration-200">
|
||||
{/* Image container with fixed aspect ratio */}
|
||||
<div className="w-full md:w-1/3 aspect-[16/9] overflow-hidden rounded-lg bg-background">
|
||||
<img
|
||||
src={post.data.image || '/api/placeholder/400/300'}
|
||||
alt={post.data.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content container */}
|
||||
<div className="w-full md:w-2/3 flex flex-col gap-2 md:gap-4 py-1 md:py-2">
|
||||
{/* Title and meta info */}
|
||||
<div className="space-y-1.5 md:space-y-3">
|
||||
<h2 className="text-lg md:text-2xl font-semibold text-yellow group-hover:text-purple transition-colors duration-200 line-clamp-2">
|
||||
{post.data.title}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 md:gap-3 text-sm md:text-base text-foreground/80">
|
||||
<span className="text-orange">{post.data.author}</span>
|
||||
<span className="text-foreground/50">•</span>
|
||||
<time dateTime={post.data.date} className="text-blue">
|
||||
{formatDate(post.data.date)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-foreground/90 text-sm md:text-lg leading-relaxed line-clamp-2 mt-0.5 md:mt-0">
|
||||
{post.data.description}
|
||||
</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-1.5 md:gap-3 mt-1 md:mt-2">
|
||||
{post.data.tags.slice(0, 3).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = `/blog/tag/${tag}`;
|
||||
}}
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
{post.data.tags.length > 3 && (
|
||||
<span className="text-xs md:text-base text-foreground/60">
|
||||
+{post.data.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<div className="mt-2 text-sm text-gray-600">
|
||||
<span>{post.data.author}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<time dateTime={post.data.date}>
|
||||
{formatDate(post.data.date)}
|
||||
</time>
|
||||
</div>
|
||||
<p className="mt-3 text-gray-700">
|
||||
{post.data.description}
|
||||
</p>
|
||||
<div className="mt-3 space-x-2">
|
||||
{post.data.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-sm text-gray-600"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -4,6 +4,7 @@ description: A quick introduction to generics with TypeScript
|
||||
author: Timothy Pidashev
|
||||
tags: [typescript]
|
||||
date: December 25, 2021
|
||||
image: "/path/to/your/image.jpg"
|
||||
---
|
||||
|
||||
In this quick post we'll cover what are generics and how to use them with TypeScript. It answers a question I got from a friend, and I thought it could also be useful for others. We'll go through the following points:
|
||||
|
||||
@@ -8,6 +8,7 @@ export const collections = {
|
||||
author: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
date: z.string(),
|
||||
image: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
projects: defineCollection({
|
||||
|
||||
1
src/src/content/darkbox.mdx
Normal file
1
src/src/content/darkbox.mdx
Normal file
@@ -0,0 +1 @@
|
||||
My gruvbox theme, with a pure black background
|
||||
0
src/src/content/fhccenter.mdx
Normal file
0
src/src/content/fhccenter.mdx
Normal file
0
src/src/content/iridescent.mdx
Normal file
0
src/src/content/iridescent.mdx
Normal file
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "AI Code Review Assistant"
|
||||
description: "An AI-powered code review tool that integrates with GitHub to provide automated code analysis and suggestions"
|
||||
githubUrl: "https://github.com/username/ai-code-reviewer"
|
||||
techStack: ["Python", "OpenAI API", "GitHub API", "FastAPI"]
|
||||
date: "2024-02-28"
|
||||
image: "/projects/ai-reviewer-preview.png"
|
||||
---
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
title: "Embedded Rust Framework"
|
||||
description: "A framework for writing embedded systems applications in Rust with a focus on safety and performance"
|
||||
githubUrl: "https://github.com/username/embedded-rust"
|
||||
techStack: ["Rust", "Embedded Systems", "RTOS", "HAL"]
|
||||
date: "2024-01-15"
|
||||
image: "/projects/embedded-preview.png"
|
||||
---
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "LLM Fine-tuning Pipeline"
|
||||
description: "A toolkit for fine-tuning large language models on custom datasets with optimized training procedures"
|
||||
githubUrl: "https://github.com/username/llm-fine-tuning"
|
||||
demoUrl: "https://llm-tuning.demo.dev"
|
||||
techStack: ["Python", "PyTorch", "CUDA", "MLFlow"]
|
||||
date: "2024-01-01"
|
||||
image: "/projects/llm-preview.png"
|
||||
---
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "Real-time Collaboration Editor"
|
||||
description: "A collaborative text editor with real-time synchronization using WebTransport protocol for minimal latency"
|
||||
githubUrl: "https://github.com/username/real-time-collab"
|
||||
demoUrl: "https://collab.demo.dev"
|
||||
techStack: ["TypeScript", "React", "WebTransport", "CRDT"]
|
||||
date: "2024-02-10"
|
||||
image: "/projects/collab-preview.png"
|
||||
---
|
||||
179
src/src/content/projects/reviveauto.mdx
Normal file
179
src/src/content/projects/reviveauto.mdx
Normal file
@@ -0,0 +1,179 @@
|
||||
---
|
||||
title: "Revive Auto Parts"
|
||||
description: "A car parts listing site built for a client."
|
||||
demoUrl: "https://reviveauto.parts"
|
||||
techStack: ["Typescript", "Tanstack Router", "React Query"]
|
||||
date: "2024-12-15"
|
||||
image: "/projects/reviveauto/thumbnail.jpeg"
|
||||
---
|
||||
|
||||
## Overview
|
||||
A car parts listing website built to provide an intuitive and efficient experience
|
||||
for users searching for automotive parts. This project was commissioned by a client
|
||||
and showcases some pretty cool modern web technologies, enabling excellent
|
||||
performance and a clean user interface.
|
||||
|
||||
## Key Features
|
||||
* **Dynamic & Simple Routing**: Powered by TanStack Router, enabling seamless navigation
|
||||
and deep-linking for product categories and details.
|
||||
|
||||
* **Real-Time Data Fetching**: Utilized React Query to handle server state and caching,
|
||||
ensuring users have up-to-date information.
|
||||
|
||||
* **Infinite Scroll**: Implemented React Query's infinite scrolling and memoization
|
||||
optimization techniques to ensure fast and seamless scrolling through listings.
|
||||
|
||||
* **Dynamic Filters**: Created dynamic filters which are editable on the admin panel,
|
||||
allowing for a high level of customization.
|
||||
|
||||
* **Responsive Design**: Built with Tailwind and Shadcn for a fully responsive layout,
|
||||
providing a consistent experience across desktop and mobile devices.
|
||||
|
||||
* **Optimized Performance**: Leveraged Vite for fast builds and optimized code-splitting,
|
||||
improving page load times drastically.
|
||||
|
||||
## Development Highlights
|
||||
I had numerous highlights and *aha* moments when developing this site. One of these has to
|
||||
be the site layout, built with [shadcn/ui](https://ui.shadcn.com) components. I had used
|
||||
this component library in a previous site, but I had yet to grasp just how powerful this
|
||||
collection of components is. We truly do stand on the shoulders of giants, and using this
|
||||
library not only allowed me to very quickly prototype a design, but to then flesh it out
|
||||
without having to dive into the weeds of UI development.
|
||||
|
||||
Another great highlight has to be [Tanstack Router](https://tanstack.com/router/latest).
|
||||
As a seasoned developer, I have had many opportunities to try a lot of different routers
|
||||
across several frameworks. As many have before me, we stumble onto the nextjs router, and
|
||||
tend not to look back. However, tanstack did something I did not expect, and it takes routing
|
||||
to the next level. With TanStack, the routes folder is solely focused on defining routes and
|
||||
layouts, providing a cleaner, more modular structure. This is a stark contrast to Next.js's
|
||||
approach, where the app directory can quickly become convoluted by mixing route definitions
|
||||
with server-side logic, API calls, and other concerns. Anybody who has built a Nextjs project
|
||||
bigger than a To-Do app can likely relate to the mental pain that is trying to find a route or
|
||||
endpoint in your app router when its nested and hidden away four or five directories deep.
|
||||
|
||||
Another memorable highlight was writing my backend in Python using [Fastapi](https://fastapi.tiangolo.com/).
|
||||
Sometimes, a project doesn't need a complex nodejs runtime, or an ORM built for a massive service.
|
||||
As a python enthusiast, I found the combination of fastapi & sqlmodel to be just perfect for this project,
|
||||
and defining api endpoints and schemas were quite enjoyable. As I do, I decided to roll my own authentication,
|
||||
and found Python to be a great environment in which to do so.
|
||||
|
||||
Lastly, I have to touch on React Query. The combo that is React Query and Fastapi can truly be magical.
|
||||
To truly showcase what I mean, here's an example of a query and endpoint working together:
|
||||
|
||||
```typescript
|
||||
// features/auth/services/login/queries.ts
|
||||
export function useLogin() {
|
||||
const { setCredentials } = useUserStore();
|
||||
|
||||
return useMutation<LoginResponseSchemaType, AxiosError<BackendError>, LoginRequestSchemaType>({
|
||||
mutationFn: user => login(user),
|
||||
onSuccess: data => {
|
||||
setCredentials({
|
||||
accessToken: data.access_token,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const login = backend<z.infer<typeof LoginRequestSchema>, z.infer<typeof LoginResponseSchema>>({
|
||||
method: "POST",
|
||||
path: `${BACKEND_URL}${AUTH}/login`,
|
||||
requestSchema: LoginRequestSchema,
|
||||
responseSchema: LoginResponseSchema,
|
||||
type: "public",
|
||||
});
|
||||
```
|
||||
```typescript
|
||||
// features/auth/services/login/schemas
|
||||
import { z } from "zod";
|
||||
|
||||
export const LoginRequestSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, "Email is required!")
|
||||
.trim()
|
||||
.email({ message: "Invalid email!" })
|
||||
.toLowerCase(),
|
||||
password: z.string().trim().min(8, { message: "Password must be at least 8 characters long!" }),
|
||||
});
|
||||
export type LoginRequestSchemaType = z.infer<typeof LoginRequestSchema>;
|
||||
|
||||
export const LoginResponseSchema = z.object({
|
||||
access_token: z.string(),
|
||||
});
|
||||
export type LoginResponseSchemaType = z.infer<typeof LoginResponseSchema>;
|
||||
```
|
||||
|
||||
```python
|
||||
# routes/auth.py
|
||||
class LoginResponse(BaseModel):
|
||||
access_token: str
|
||||
|
||||
@router.post(
|
||||
"/login",
|
||||
description=
|
||||
"""
|
||||
The login endpoint.
|
||||
|
||||
Used to create access and refresh tokens for a user.
|
||||
The refresh token is set as an HTTP-only cookie.
|
||||
""",
|
||||
summary="Create access token and set refresh token as HTTP-only cookie."
|
||||
)
|
||||
async def login(
|
||||
response: Response,
|
||||
form_data: LoginSchema = Body(...),
|
||||
session: Session = Depends(get_session)
|
||||
) -> Any:
|
||||
|
||||
# Fetch user using the form_data sent by the client
|
||||
user = await service.authenticate(
|
||||
email=form_data.email,
|
||||
password=form_data.password,
|
||||
session=session
|
||||
)
|
||||
|
||||
# If service returns None, raise exception
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
)
|
||||
|
||||
# Set refresh token as HTTP-only cookie
|
||||
response.set_cookie(
|
||||
key="refresh_token",
|
||||
value=create_token(user, type="refresh"),
|
||||
httponly=True,
|
||||
path="/auth/refresh",
|
||||
domain=Config.JWT_COOKIE_DOMAIN,
|
||||
expires=datetime.now(timezone.utc) + Config.JWT_REFRESH_EXPIRES
|
||||
)
|
||||
|
||||
return LoginResponse(
|
||||
access_token=create_token(user, type="access")
|
||||
)
|
||||
```
|
||||
Using Zod, the frontend is validating what a user is attempting to submit
|
||||
before calling the endpoint. If Zod does not throw an error, our frontend will
|
||||
call our backend endpoint, which also expects the right types to be present.
|
||||
Afterwards, the backend will respond back with a response that our frontend
|
||||
will also validate. This allows for a complete multi-directional
|
||||
request -> response type validation!
|
||||
|
||||
## Challenges and Roadblocks
|
||||
For the most part, there were really no challenges, say for some hiccups here and there.
|
||||
Probably the most painful parts were creating unit tests for the frontend, and scraping
|
||||
Facebook for a total of 1,384 posts for my client, who wanted the posts imported over.
|
||||
As one can imagine, that process is not simple to do manually by hand, so I wrote multiple
|
||||
python scriptsusing the seleneum library to fetch the posts from the sellers account, a process
|
||||
which took multiple attempts, several overnight scrapes, and lots of data sanitation afterwards.
|
||||
Other than that, everything else was an absolute joy to work on, and I finished the project in under
|
||||
15K LOC.
|
||||
|
||||
## Summary
|
||||
Sometimes, building a CRUD app can be lots of fun, no matter how many times you've done it before.
|
||||
All it takes is adding something new to the stack, and striving to improve the code you write. This
|
||||
project was exactly that, a mix of new technologies working together to power a fairly neat site,
|
||||
which can be viewed [here](https://reviveauto.parts). Maybe there is a car part there which the reader
|
||||
might need. As always, thanks for the read :)
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: "Project Name"
|
||||
description: "Project description here"
|
||||
demoUrl: "https://demo.project.com" # optional
|
||||
techStack: ["React", "TypeScript", "Tailwind"]
|
||||
date: "2024-03-15"
|
||||
image: "/projects/preview.png" # optional
|
||||
---
|
||||
|
||||
# Project Overview
|
||||
|
||||
This is a detailed description of the project...
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Technical Details
|
||||
|
||||
Here's how it works...
|
||||
|
||||
## Challenges & Solutions
|
||||
|
||||
During development...
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
title: "Rust WASM Game Engine"
|
||||
description: "A lightweight 2D game engine built with Rust and compiled to WebAssembly for high-performance browser games"
|
||||
githubUrl: "https://github.com/username/rust-wasm-game"
|
||||
demoUrl: "https://rust-wasm-game.demo.dev"
|
||||
techStack: ["Rust", "WebAssembly", "Canvas API", "Web Workers"]
|
||||
date: "2024-03-15"
|
||||
image: "/projects/rust-wasm-preview.png"
|
||||
---
|
||||
|
||||
# Project Overview
|
||||
|
||||
This is a detailed description of the project...
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Technical Details
|
||||
|
||||
Here's how it works...
|
||||
|
||||
## Challenges & Solutions
|
||||
|
||||
During development...
|
||||
@@ -50,7 +50,7 @@ const { Content } = await project.render();
|
||||
{project.data.demoUrl && (
|
||||
<a href={project.data.demoUrl}
|
||||
class="text-foreground/70 hover:text-green-bright transition-colors">
|
||||
Live Demo
|
||||
Live Link
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user