Clean up page routing: /work for portfolio, /posts for blog

This commit is contained in:
Kunthawat Greethong
2026-05-01 17:24:26 +07:00
parent e921b82d9e
commit b2a380eb4d
8 changed files with 53 additions and 458 deletions

View File

@@ -42,7 +42,7 @@
"runtime": null "runtime": null
}, },
"build": { "build": {
"buildCommand": "pnpm build 2>&1 | tail -20", "buildCommand": "pnpm build 2>&1 | tail -25",
"testCommand": null, "testCommand": null,
"lintCommand": null, "lintCommand": null,
"devCommand": "npm run dev", "devCommand": "npm run dev",
@@ -138,6 +138,12 @@
"lastAccessed": 1777620637431, "lastAccessed": 1777620637431,
"type": "file" "type": "file"
}, },
{
"path": "src/pages/posts/index.astro",
"accessCount": 3,
"lastAccessed": 1777630959283,
"type": "file"
},
{ {
"path": "src/styles/theme.css", "path": "src/styles/theme.css",
"accessCount": 2, "accessCount": 2,
@@ -150,18 +156,36 @@
"lastAccessed": 1777618173372, "lastAccessed": 1777618173372,
"type": "file" "type": "file"
}, },
{
"path": "src/pages/posts/index.astro",
"accessCount": 2,
"lastAccessed": 1777618215078,
"type": "file"
},
{ {
"path": "src/layouts/BlogBase.astro", "path": "src/layouts/BlogBase.astro",
"accessCount": 2, "accessCount": 2,
"lastAccessed": 1777620599150, "lastAccessed": 1777620599150,
"type": "file" "type": "file"
}, },
{
"path": "src/pages/work/[slug].astro",
"accessCount": 2,
"lastAccessed": 1777630917909,
"type": "file"
},
{
"path": "src/pages/portfolio-index.astro",
"accessCount": 2,
"lastAccessed": 1777630938418,
"type": "file"
},
{
"path": "src/pages/about.astro",
"accessCount": 2,
"lastAccessed": 1777631018052,
"type": "file"
},
{
"path": "src/pages/posts/[slug].astro",
"accessCount": 2,
"lastAccessed": 1777631024601,
"type": "file"
},
{ {
"path": "src/pages/index.astro", "path": "src/pages/index.astro",
"accessCount": 1, "accessCount": 1,
@@ -180,24 +204,6 @@
"lastAccessed": 1777616192120, "lastAccessed": 1777616192120,
"type": "file" "type": "file"
}, },
{
"path": "src/pages/portfolio-index.astro",
"accessCount": 1,
"lastAccessed": 1777616195418,
"type": "file"
},
{
"path": "src/pages/work/[slug].astro",
"accessCount": 1,
"lastAccessed": 1777616198707,
"type": "file"
},
{
"path": "src/pages/about.astro",
"accessCount": 1,
"lastAccessed": 1777616201998,
"type": "file"
},
{ {
"path": "src/pages/contact.astro", "path": "src/pages/contact.astro",
"accessCount": 1, "accessCount": 1,
@@ -216,12 +222,6 @@
"lastAccessed": 1777616259549, "lastAccessed": 1777616259549,
"type": "file" "type": "file"
}, },
{
"path": "src/pages/posts/[slug].astro",
"accessCount": 1,
"lastAccessed": 1777618182408,
"type": "file"
},
{ {
"path": "src/pages/posts/posts-index.astro", "path": "src/pages/posts/posts-index.astro",
"accessCount": 1, "accessCount": 1,

View File

@@ -9,3 +9,5 @@
{"t":0,"agent":"a42b6ce","agent_type":"unknown","event":"agent_stop","success":true} {"t":0,"agent":"a42b6ce","agent_type":"unknown","event":"agent_stop","success":true}
{"t":0,"agent":"a3d22ed","agent_type":"unknown","event":"agent_stop","success":true} {"t":0,"agent":"a3d22ed","agent_type":"unknown","event":"agent_stop","success":true}
{"t":0,"agent":"ad5c4bf","agent_type":"unknown","event":"agent_stop","success":true} {"t":0,"agent":"ad5c4bf","agent_type":"unknown","event":"agent_stop","success":true}
{"t":0,"agent":"a2608de","agent_type":"unknown","event":"agent_stop","success":true}
{"t":0,"agent":"a85220d","agent_type":"unknown","event":"agent_stop","success":true}

View File

@@ -1,5 +1,5 @@
{ {
"updatedAt": "2026-05-01T07:29:47.145Z", "updatedAt": "2026-05-01T10:20:22.969Z",
"missions": [ "missions": [
{ {
"id": "session:33698839-2ad1-4412-9735-43676f5e6beb:none", "id": "session:33698839-2ad1-4412-9735-43676f5e6beb:none",
@@ -7,7 +7,7 @@
"name": "none", "name": "none",
"objective": "Session mission", "objective": "Session mission",
"createdAt": "2026-04-30T23:41:28.830Z", "createdAt": "2026-04-30T23:41:28.830Z",
"updatedAt": "2026-05-01T07:29:47.145Z", "updatedAt": "2026-05-01T10:20:22.969Z",
"status": "done", "status": "done",
"workerCount": 1, "workerCount": 1,
"taskCounts": { "taskCounts": {
@@ -27,7 +27,7 @@
"currentStep": null, "currentStep": null,
"latestUpdate": "completed", "latestUpdate": "completed",
"completedSummary": null, "completedSummary": null,
"updatedAt": "2026-05-01T07:29:47.145Z" "updatedAt": "2026-05-01T10:20:22.969Z"
} }
], ],
"timeline": [ "timeline": [
@@ -110,6 +110,22 @@
"agent": "Explore:a0f2988", "agent": "Explore:a0f2988",
"detail": "completed", "detail": "completed",
"sourceKey": "session-stop:ad5c4bf39e4876c02" "sourceKey": "session-stop:ad5c4bf39e4876c02"
},
{
"id": "session-stop:a2608de5f03163d75:2026-05-01T07:32:23.209Z",
"at": "2026-05-01T07:32:23.209Z",
"kind": "completion",
"agent": "Explore:a0f2988",
"detail": "completed",
"sourceKey": "session-stop:a2608de5f03163d75"
},
{
"id": "session-stop:a85220dfcd04dafee:2026-05-01T10:20:22.969Z",
"at": "2026-05-01T10:20:22.969Z",
"kind": "completion",
"agent": "Explore:a0f2988",
"detail": "completed",
"sourceKey": "session-stop:a85220dfcd04dafee"
} }
] ]
} }

View File

@@ -13,5 +13,5 @@
"total_spawned": 1, "total_spawned": 1,
"total_completed": 1, "total_completed": 1,
"total_failed": 0, "total_failed": 0,
"last_updated": "2026-05-01T07:29:47.246Z" "last_updated": "2026-05-01T10:20:23.070Z"
} }

View File

@@ -1,65 +0,0 @@
---
import { getEmDashEntry } from "emdash";
import PortfolioBase from "../layouts/PortfolioBase.astro";
const { entry: page, cacheHint } = await getEmDashEntry("pages", "about");
Astro.cache.set(cacheHint);
---
<PortfolioBase title="About">
<main class="container">
<article>
<h1>About Us</h1>
{page?.data?.content ? (
<div class="content">
{page.data.content.map((block: any) => {
if (block._type === "block") {
return <p>{block.children?.map((c: any) => c.text).join("")}</p>;
}
return null;
})}
</div>
) : (
<div class="content">
<p>We are a creative studio focused on design and development.</p>
<p>Add your about page content in the admin panel.</p>
<a href="/_emdash/admin">Open Admin</a>
</div>
)}
</article>
</main>
</PortfolioBase>
<style>
main.container {
padding: var(--spacing-5xl) var(--spacing-lg);
max-width: 800px;
margin: 0 auto;
}
article h1 {
font-size: var(--font-size-4xl);
margin-bottom: var(--spacing-2xl);
font-family: var(--font-serif);
}
.content {
font-size: var(--font-size-lg);
line-height: 1.7;
}
.content p {
margin-bottom: var(--spacing-lg);
}
.content a {
display: inline-block;
margin-top: var(--spacing-lg);
padding: var(--spacing-md) var(--spacing-xl);
background: var(--color-primary);
color: white;
text-decoration: none;
border-radius: var(--radius);
font-weight: 600;
}
</style>

View File

@@ -1,160 +0,0 @@
---
import PortfolioBase from "../layouts/PortfolioBase.astro";
let formStatus: "idle" | "success" | "error" = "idle";
let formMessage = "";
if (Astro.request.method === "POST") {
try {
const formData = await Astro.request.formData();
const name = formData.get("name")?.toString() || "";
const email = formData.get("email")?.toString() || "";
const message = formData.get("message")?.toString() || "";
if (!name || !email || !message) {
formStatus = "error";
formMessage = "Please fill in all fields.";
} else if (!email.includes("@")) {
formStatus = "error";
formMessage = "Please enter a valid email address.";
} else {
console.log("Contact form submission:", { name, email, message });
formStatus = "success";
formMessage = "Thanks for reaching out! We'll get back to you soon.";
}
} catch {
formStatus = "error";
formMessage = "Something went wrong. Please try again.";
}
}
---
<PortfolioBase title="Contact">
<main class="container">
<h1>Get in Touch</h1>
{formStatus === "success" ? (
<div class="success-message">
<h2>Message Sent</h2>
<p>{formMessage}</p>
<a href="/contact">Send another message</a>
</div>
) : (
<form method="POST" class="contact-form">
{formStatus === "error" && (
<p class="error">{formMessage}</p>
)}
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" placeholder="Your name" required />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="your@email.com" required />
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" name="message" placeholder="Tell us about your project" required></textarea>
</div>
<button type="submit">Send Message</button>
</form>
)}
</main>
</PortfolioBase>
<style>
main.container {
padding: var(--spacing-5xl) var(--spacing-lg);
max-width: 600px;
margin: 0 auto;
}
h1 {
font-size: var(--font-size-4xl);
margin-bottom: var(--spacing-2xl);
font-family: var(--font-serif);
text-align: center;
}
.contact-form {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
.form-group {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
label {
font-weight: 600;
font-size: var(--font-size-sm);
}
input,
textarea {
padding: var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius);
font-size: var(--font-size-base);
background: var(--color-bg);
color: var(--color-text);
}
input:focus,
textarea:focus {
outline: none;
border-color: var(--color-primary);
}
textarea {
min-height: 150px;
resize: vertical;
}
button {
padding: var(--spacing-md) var(--spacing-xl);
background: var(--color-primary);
color: white;
border: none;
border-radius: var(--radius);
font-weight: 600;
cursor: pointer;
transition: background var(--transition-fast);
}
button:hover {
background: var(--color-primary-dark);
}
.error {
color: var(--color-error);
padding: var(--spacing-md);
background: rgba(239, 68, 68, 0.1);
border-radius: var(--radius);
}
.success-message {
text-align: center;
padding: var(--spacing-4xl) 0;
}
.success-message h2 {
font-size: var(--font-size-2xl);
margin-bottom: var(--spacing-md);
font-family: var(--font-serif);
}
.success-message a {
display: inline-block;
margin-top: var(--spacing-lg);
padding: var(--spacing-md) var(--spacing-xl);
background: var(--color-primary);
color: white;
text-decoration: none;
border-radius: var(--radius);
font-weight: 600;
}
</style>

View File

@@ -1,198 +0,0 @@
---
import { getEmDashCollection, getTermsForEntries } from "emdash";
import BlogBase from "../../layouts/BlogBase.astro";
import { getReadingTime } from "../../utils/reading-time";
const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
});
Astro.cache.set(cacheHint);
const tagsByEntry = await getTermsForEntries(
"posts",
posts.map((p) => p.data.id),
"tag",
);
const postsWithTags = posts.map((post) => ({
post,
tags: tagsByEntry.get(post.data.id) ?? [],
bylines: post.data.bylines ?? [],
}));
const formatDate = (date: Date) =>
date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
---
<BlogBase title="All Posts" description="Browse all posts">
<div class="posts-list-page">
<h1>All Posts</h1>
{posts.length === 0 ? (
<p class="empty">No posts yet.</p>
) : (
<div class="post-list">
{postsWithTags.map(({ post, tags, bylines }) => (
<article class="post-card">
<header>
{bylines.length > 0 && (
<div class="byline-avatars">
{bylines.slice(0, 2).map((credit, index) => (
<>
{index > 0 && <span>, </span>}
{credit.byline.avatarMediaId && (
<img
src={`/_emdash/api/media/file/${credit.byline.avatarMediaId}`}
alt={credit.byline.displayName}
/>
)}
{credit.byline.displayName}
</>
))}
{bylines.length > 2 && (
<span>+{bylines.length - 2}</span>
)}
</div>
)}
{post.data.publishedAt && (
<time datetime={post.data.publishedAt.toISOString()}>
{formatDate(post.data.publishedAt)}
</time>
)}
<span>{getReadingTime(post.data.content)} min read</span>
</header>
<h2>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
</h2>
<hr />
{post.data.excerpt && (
<p class="post-excerpt">{post.data.excerpt}</p>
)}
<a href={`/posts/${post.id}`} class="read-more">Read more →</a>
{tags.length > 0 && (
<div class="tag-list">
{tags.slice(0, 3).map((t) => (
<a href={`/tag/${t.slug}`} class="tag">{t.label}</a>
))}
</div>
)}
</article>
))}
</div>
)}
</div>
</BlogBase>
<style>
.posts-list-page {
max-width: var(--content-width, 680px);
margin: 0 auto;
padding: var(--spacing-8, 2rem);
}
h1 {
font-size: var(--font-size-4xl, 2.5rem);
font-weight: 700;
margin-bottom: var(--spacing-8, 2rem);
letter-spacing: var(--tracking-tight, -0.03em);
}
.empty {
color: var(--color-muted, #8b8b8b);
padding: var(--spacing-12, 3rem) 0;
text-align: center;
}
.post-card {
padding: var(--spacing-6, 1.5rem) 0;
border-bottom: 1px solid var(--color-border, #e5e5e5);
}
.post-card header {
display: flex;
gap: var(--spacing-3, 0.75rem);
align-items: center;
flex-wrap: wrap;
font-size: var(--font-size-sm, 0.875rem);
color: var(--color-muted, #8b8b8b);
margin-bottom: var(--spacing-2, 0.5rem);
}
.byline-avatars {
display: flex;
align-items: center;
gap: var(--spacing-2, 0.5rem);
}
.byline-avatars img {
width: 20px;
height: 20px;
border-radius: 50%;
}
.post-card h2 {
font-size: var(--font-size-2xl, 1.5rem);
font-weight: 600;
line-height: var(--leading-snug, 1.3);
letter-spacing: var(--tracking-snug, -0.02em);
margin-bottom: var(--spacing-2, 0.5rem);
}
.post-card h2 a {
color: var(--color-text, #1a1a1a);
text-decoration: none;
}
.post-card h2 a:hover {
color: var(--color-primary, #0066cc);
}
.post-card hr {
border: none;
border-top: 1px solid var(--color-border, #e5e5e5);
margin: var(--spacing-4, 1rem) 0;
}
.post-excerpt {
color: var(--color-text-secondary, #525252);
line-height: var(--leading-normal, 1.5);
margin-bottom: var(--spacing-4, 1rem);
}
.read-more {
display: inline-block;
color: var(--color-primary, #0066cc);
text-decoration: none;
font-weight: 500;
margin-bottom: var(--spacing-4, 1rem);
}
.read-more:hover {
text-decoration: underline;
}
.tag-list {
display: flex;
gap: var(--spacing-2, 0.5rem);
flex-wrap: wrap;
}
.tag {
font-size: var(--font-size-xs, 0.8125rem);
padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);
background: var(--color-surface, #f7f7f7);
color: var(--color-text-secondary, #525252);
border-radius: var(--radius, 4px);
text-decoration: none;
transition: all var(--transition-fast, 120ms ease);
}
.tag:hover {
background: var(--color-primary, #0066cc);
color: white;
}
</style>