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;
|
date: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
description: string;
|
description: string;
|
||||||
|
image?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,36 +26,70 @@ const formatDate = (dateString: string) => {
|
|||||||
|
|
||||||
export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto px-4 py-8">
|
<div className="w-full max-w-4xl mx-auto">
|
||||||
<ul className="space-y-8">
|
<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) => (
|
{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
|
<a
|
||||||
href={`/blog/${post.slug}`}
|
href={`/blog/${post.slug}`}
|
||||||
className="text-xl font-semibold hover:text-blue-600 transition-colors duration-200"
|
className="block"
|
||||||
>
|
>
|
||||||
|
<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}
|
{post.data.title}
|
||||||
</a>
|
</h2>
|
||||||
<div className="mt-2 text-sm text-gray-600">
|
|
||||||
<span>{post.data.author}</span>
|
<div className="flex flex-wrap items-center gap-2 md:gap-3 text-sm md:text-base text-foreground/80">
|
||||||
<span className="mx-2">•</span>
|
<span className="text-orange">{post.data.author}</span>
|
||||||
<time dateTime={post.data.date}>
|
<span className="text-foreground/50">•</span>
|
||||||
|
<time dateTime={post.data.date} className="text-blue">
|
||||||
{formatDate(post.data.date)}
|
{formatDate(post.data.date)}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-gray-700">
|
</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}
|
{post.data.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 space-x-2">
|
|
||||||
{post.data.tags.map((tag) => (
|
{/* 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
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="text-sm text-gray-600"
|
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}
|
#{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
{post.data.tags.length > 3 && (
|
||||||
|
<span className="text-xs md:text-base text-foreground/60">
|
||||||
|
+{post.data.tags.length - 3}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ description: A quick introduction to generics with TypeScript
|
|||||||
author: Timothy Pidashev
|
author: Timothy Pidashev
|
||||||
tags: [typescript]
|
tags: [typescript]
|
||||||
date: December 25, 2021
|
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:
|
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(),
|
author: z.string(),
|
||||||
tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
date: z.string(),
|
date: z.string(),
|
||||||
|
image: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
projects: defineCollection({
|
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 && (
|
{project.data.demoUrl && (
|
||||||
<a href={project.data.demoUrl}
|
<a href={project.data.demoUrl}
|
||||||
class="text-foreground/70 hover:text-green-bright transition-colors">
|
class="text-foreground/70 hover:text-green-bright transition-colors">
|
||||||
Live Demo
|
Live Link
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user