feat: recover portfolio pages from scratch workspaces
- Add base layouts (BaseLayout, BlogLayout, ProjectLayout) - Add UI components (Header, Footer, Navigation, Card, Tag) - Add About page with personal story - Add Projects pages (index, detail) - Add homepage content - Add SEO files (robots.txt, webmanifest) Work was done by agents in isolated workspaces. Consolidated into main repo for proper git tracking.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import ProjectLayout from '../../layouts/ProjectLayout.astro';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const projects = await getCollection('projects');
|
||||
return projects
|
||||
.filter(p => !p.data.draft)
|
||||
.map(project => ({
|
||||
params: { slug: project.slug },
|
||||
props: { project },
|
||||
}));
|
||||
}
|
||||
|
||||
const { project } = Astro.props;
|
||||
const { Content } = await project.render();
|
||||
---
|
||||
|
||||
<ProjectLayout {...project.data}>
|
||||
<Content />
|
||||
</ProjectLayout>
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import Header from '../../components/layout/Header.astro';
|
||||
import Footer from '../../components/layout/Footer.astro';
|
||||
import Card from '../../components/ui/Card.astro';
|
||||
import Tag from '../../components/ui/Tag.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const allProjects = (await getCollection('projects'))
|
||||
.filter(p => !p.data.draft)
|
||||
.sort((a, b) => b.data.startDate.getTime() - a.data.startDate.getTime());
|
||||
|
||||
const featured = allProjects.filter(p => p.data.featured);
|
||||
const active = allProjects.filter(p => p.data.status === 'active' && !p.data.featured);
|
||||
const others = allProjects.filter(p => p.data.status !== 'active' && !p.data.featured);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Proyectos"
|
||||
description="Portfolio de productos SaaS: WarrantyHub, Garage61 API, y más proyectos en desarrollo"
|
||||
>
|
||||
<Header />
|
||||
<main class="flex-1 max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<header class="mb-12">
|
||||
<h1 class="text-4xl font-bold mb-4">Proyectos</h1>
|
||||
<p class="text-xl text-text-secondary max-w-3xl">
|
||||
Portfolio de productos SaaS construidos en público. Desde ideas hasta
|
||||
productos escalables generando revenue recurrente.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{featured.length > 0 && (
|
||||
<section class="mb-16">
|
||||
<h2 class="text-2xl font-semibold mb-6 text-primary">🌟 Proyectos Destacados</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
{featured.map(project => (
|
||||
<Card href={`/projects/${project.slug}`} class="flex flex-col">
|
||||
{project.data.image && (
|
||||
<img
|
||||
src={project.data.image}
|
||||
alt={project.data.title}
|
||||
class="w-full h-48 object-cover rounded-t-lg -m-6 mb-4"
|
||||
/>
|
||||
)}
|
||||
<h3 class="text-2xl font-semibold mb-2 text-text-primary">
|
||||
{project.data.title}
|
||||
</h3>
|
||||
<p class="text-text-secondary mb-4 flex-1">
|
||||
{project.data.description}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{project.data.tags.slice(0, 4).map(tag => (
|
||||
<Tag label={tag} variant="primary" />
|
||||
))}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-accent-green font-medium capitalize">
|
||||
{project.data.status}
|
||||
</span>
|
||||
<span class="text-primary text-sm font-medium">
|
||||
Ver proyecto →
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{active.length > 0 && (
|
||||
<section class="mb-16">
|
||||
<h2 class="text-2xl font-semibold mb-6">Proyectos Activos</h2>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{active.map(project => (
|
||||
<Card href={`/projects/${project.slug}`}>
|
||||
<h3 class="text-xl font-semibold mb-2 text-text-primary">
|
||||
{project.data.title}
|
||||
</h3>
|
||||
<p class="text-text-secondary mb-4">
|
||||
{project.data.description}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{project.data.tags.slice(0, 3).map(tag => (
|
||||
<Tag label={tag} variant="neutral" />
|
||||
))}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-accent-green font-medium">Activo</span>
|
||||
<span class="text-primary text-sm font-medium">Ver →</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{others.length > 0 && (
|
||||
<section>
|
||||
<h2 class="text-2xl font-semibold mb-6">Otros Proyectos</h2>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{others.map(project => (
|
||||
<Card href={`/projects/${project.slug}`}>
|
||||
<h3 class="text-xl font-semibold mb-2 text-text-primary">
|
||||
{project.data.title}
|
||||
</h3>
|
||||
<p class="text-text-secondary mb-4">
|
||||
{project.data.description}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{project.data.tags.slice(0, 3).map(tag => (
|
||||
<Tag label={tag} variant="neutral" />
|
||||
))}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-text-tertiary capitalize">
|
||||
{project.data.status}
|
||||
</span>
|
||||
<span class="text-primary text-sm font-medium">Ver →</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</main>
|
||||
<Footer />
|
||||
</BaseLayout>
|
||||
Reference in New Issue
Block a user