Clean up page routing: /work for portfolio, /posts for blog
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
"runtime": null
|
||||
},
|
||||
"build": {
|
||||
"buildCommand": "pnpm build 2>&1 | tail -20",
|
||||
"buildCommand": "pnpm build 2>&1 | tail -25",
|
||||
"testCommand": null,
|
||||
"lintCommand": null,
|
||||
"devCommand": "npm run dev",
|
||||
@@ -138,6 +138,12 @@
|
||||
"lastAccessed": 1777620637431,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/posts/index.astro",
|
||||
"accessCount": 3,
|
||||
"lastAccessed": 1777630959283,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/styles/theme.css",
|
||||
"accessCount": 2,
|
||||
@@ -150,18 +156,36 @@
|
||||
"lastAccessed": 1777618173372,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/posts/index.astro",
|
||||
"accessCount": 2,
|
||||
"lastAccessed": 1777618215078,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/layouts/BlogBase.astro",
|
||||
"accessCount": 2,
|
||||
"lastAccessed": 1777620599150,
|
||||
"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",
|
||||
"accessCount": 1,
|
||||
@@ -180,24 +204,6 @@
|
||||
"lastAccessed": 1777616192120,
|
||||
"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",
|
||||
"accessCount": 1,
|
||||
@@ -216,12 +222,6 @@
|
||||
"lastAccessed": 1777616259549,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/posts/[slug].astro",
|
||||
"accessCount": 1,
|
||||
"lastAccessed": 1777618182408,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/posts/posts-index.astro",
|
||||
"accessCount": 1,
|
||||
|
||||
@@ -9,3 +9,5 @@
|
||||
{"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":"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}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-05-01T07:29:47.145Z",
|
||||
"updatedAt": "2026-05-01T10:20:22.969Z",
|
||||
"missions": [
|
||||
{
|
||||
"id": "session:33698839-2ad1-4412-9735-43676f5e6beb:none",
|
||||
@@ -7,7 +7,7 @@
|
||||
"name": "none",
|
||||
"objective": "Session mission",
|
||||
"createdAt": "2026-04-30T23:41:28.830Z",
|
||||
"updatedAt": "2026-05-01T07:29:47.145Z",
|
||||
"updatedAt": "2026-05-01T10:20:22.969Z",
|
||||
"status": "done",
|
||||
"workerCount": 1,
|
||||
"taskCounts": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"currentStep": null,
|
||||
"latestUpdate": "completed",
|
||||
"completedSummary": null,
|
||||
"updatedAt": "2026-05-01T07:29:47.145Z"
|
||||
"updatedAt": "2026-05-01T10:20:22.969Z"
|
||||
}
|
||||
],
|
||||
"timeline": [
|
||||
@@ -110,6 +110,22 @@
|
||||
"agent": "Explore:a0f2988",
|
||||
"detail": "completed",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"total_spawned": 1,
|
||||
"total_completed": 1,
|
||||
"total_failed": 0,
|
||||
"last_updated": "2026-05-01T07:29:47.246Z"
|
||||
"last_updated": "2026-05-01T10:20:23.070Z"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user