Compare commits
7
Commits
3c92761dc6
...
b862b85fd6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b862b85fd6 | ||
|
|
4aced1227d | ||
|
|
edc17fb13c | ||
|
|
a6b0ea91cd | ||
|
|
e9586acbaa | ||
|
|
63a6523483 | ||
|
|
ddc74afc2c |
@@ -39,3 +39,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
.playwright-mcp/*
|
||||||
|
|||||||
@@ -7,3 +7,8 @@ This version has breaking changes — APIs, conventions, and file structure may
|
|||||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||||
|
|
||||||
<!-- END:nextjs-agent-rules -->
|
<!-- END:nextjs-agent-rules -->
|
||||||
|
|
||||||
|
## MCPs
|
||||||
|
|
||||||
|
- Playwright Scheenshots y cualquier cosa relacionada a Playwright tiene que estar en la carpeta .playwright-mcp.
|
||||||
|
- Context7 Usaremos este MCP para traer la documentación actualizada del framework.
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default function FeedHeader() {
|
||||||
|
return (
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="mb-1 text-[12.5px] font-extrabold tracking-[.8px] text-[#D9583C]">
|
||||||
|
GUARDERÍA · SALA SOLES
|
||||||
|
</div>
|
||||||
|
<h1 className="font-display text-[30px] font-semibold text-[#3F362E]">Buenas, Caro</h1>
|
||||||
|
<p className="mt-[5px] text-[14.5px] text-[#94887B]">12 niños · martes 17 jun</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { Post, PostCategory } from "@/app/data/feed";
|
||||||
|
|
||||||
|
/** Badge por categoría: colores exactos de la plantilla. */
|
||||||
|
const BADGES: Record<PostCategory, { label: string; classes: string }> = {
|
||||||
|
logro: { label: "LOGRO", classes: "bg-[#CFEBD8] text-[#3E9B6C]" },
|
||||||
|
actividad: { label: "ACTIVIDAD", classes: "bg-[#C7E7F1] text-[#2E89A6]" },
|
||||||
|
anuncio: { label: "ANUNCIO", classes: "bg-[#CCD8F4] text-[#4E72C8]" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colores dinámicos → utilities Tailwind con valores exactos.
|
||||||
|
* El set de pares del mock es cerrado; nunca se usan atributos style.
|
||||||
|
*/
|
||||||
|
const AVATAR_COLORS: Record<string, string> = {
|
||||||
|
"A9D9E8-1F7A93": "bg-[#A9D9E8] text-[#1F7A93]",
|
||||||
|
"CCD8F4-4E72C8": "bg-[#CCD8F4] text-[#4E72C8]",
|
||||||
|
};
|
||||||
|
|
||||||
|
function avatarClasses(post: Post): string {
|
||||||
|
const key = `${post.authorColors.bg.replace("#", "")}-${post.authorColors.text.replace("#", "")}`;
|
||||||
|
return AVATAR_COLORS[key] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PostCard({ post }: { post: Post }) {
|
||||||
|
const badge = BADGES[post.category];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="rounded-[20px] border border-[#ECE0D0] bg-[#FFFDF9] px-[22px] py-5 shadow-[0_4px_16px_-12px_rgba(120,90,60,.5)]">
|
||||||
|
{/* Encabezado: avatar + autor + badge */}
|
||||||
|
<div className="mb-[14px] flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className={`flex h-11 w-11 flex-none items-center justify-center rounded-full font-display text-[17px] font-semibold ${avatarClasses(post)}`}
|
||||||
|
>
|
||||||
|
{post.authorInitial ? (
|
||||||
|
post.authorInitial
|
||||||
|
) : (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m3 11 18-5v12L3 14v-3zM11.6 16.8a3 3 0 1 1-5.8-1.6" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="font-display text-[16.5px] font-semibold text-[#3F362E]">{post.authorName}</div>
|
||||||
|
<div className="text-[12.5px] text-[#A89A8B]">
|
||||||
|
{post.timeLabel} · {post.publishedBy}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-[7px] rounded-full px-3 py-[6px] ${badge.classes}`}>
|
||||||
|
<span className="h-2 w-2 rounded-full bg-current" />
|
||||||
|
<span className="text-xs font-extrabold tracking-[.5px]">{badge.label}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Destinatario */}
|
||||||
|
<div className="mb-2.5 text-[12.5px] text-[#A89A8B]">Para: {post.audience}</div>
|
||||||
|
|
||||||
|
{/* Contenido */}
|
||||||
|
<p className="text-[15.5px] leading-[1.55] text-[#4A4038]">{post.body}</p>
|
||||||
|
|
||||||
|
{/* Placeholder de foto (solo posts de actividad con foto) */}
|
||||||
|
{post.photoLabel && (
|
||||||
|
<Link
|
||||||
|
href="/foto"
|
||||||
|
className="mt-[14px] flex h-[200px] flex-col items-center justify-center gap-2 rounded-2xl border-[1.5px] border-dashed border-[#DBCDBA] bg-[#F4ECE1] text-[#B0A290]"
|
||||||
|
>
|
||||||
|
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||||
|
<circle cx="9" cy="9" r="2" />
|
||||||
|
<path d="m21 15-3.6-3.6a2 2 0 0 0-2.8 0L6 21" />
|
||||||
|
</svg>
|
||||||
|
<span className="text-[13.5px]">Foto · {post.photoLabel}</span>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Acciones */}
|
||||||
|
<div className="mt-4 flex items-center gap-[18px] border-t border-[#F0E6D8] pt-[14px]">
|
||||||
|
<span className="flex items-center gap-[7px] text-sm font-bold text-[#E0654A]">
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="#E0654A" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21.2l7.8-7.8 1-1a5.5 5.5 0 0 0 0-7.8z" />
|
||||||
|
</svg>
|
||||||
|
{post.likes}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
href="/detalle-publicacion"
|
||||||
|
className="flex items-center gap-[7px] text-sm font-bold text-[#94887B]"
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8z" />
|
||||||
|
</svg>
|
||||||
|
{post.comments}
|
||||||
|
</Link>
|
||||||
|
<span className="flex-1" />
|
||||||
|
<Link href="/crear-publicacion" className="text-sm font-extrabold text-[#C5503A]">
|
||||||
|
Editar
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function ShareBox() {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href="/crear-publicacion"
|
||||||
|
className="mb-6 flex items-center gap-[14px] rounded-[18px] border border-[#ECE0D0] bg-[#FFFDF9] px-[18px] py-[14px] shadow-[0_4px_14px_-10px_rgba(120,90,60,.4)]"
|
||||||
|
>
|
||||||
|
<div className="flex h-10 w-10 flex-none items-center justify-center rounded-full bg-[#F2937A] font-display text-base font-semibold text-white">
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
<span className="flex-1 text-[15px] text-[#A89A8B]">Compartí un momento…</span>
|
||||||
|
<span className="flex h-[38px] w-[38px] items-center justify-center rounded-[12px] bg-[#FBE3D8] text-[#E0654A]">
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
|
||||||
|
<circle cx="12" cy="13" r="4" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
type NavItem = {
|
||||||
|
href: string;
|
||||||
|
label: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NAV_ITEMS: NavItem[] = [
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: "Feed",
|
||||||
|
icon: (
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M3 9.5 12 3l9 6.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/ninos",
|
||||||
|
label: "Niños",
|
||||||
|
icon: (
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="9" cy="7" r="3" />
|
||||||
|
<circle cx="17" cy="9" r="2.4" />
|
||||||
|
<path d="M2.5 20a6.5 6.5 0 0 1 13 0M16 20a5 5 0 0 1 5.5-4.9" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/avisos",
|
||||||
|
label: "Avisos",
|
||||||
|
icon: (
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 0 1-3.4 0" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/mi-cuenta",
|
||||||
|
label: "Mi cuenta",
|
||||||
|
icon: (
|
||||||
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||||
|
<circle cx="12" cy="7" r="4" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Sidebar({ activeHref = "/" }: { activeHref?: string }) {
|
||||||
|
return (
|
||||||
|
<aside className="sticky top-0 flex h-screen w-[248px] flex-none flex-col border-r border-[#ECE0D0] bg-[#FFFDF9] px-4 py-6">
|
||||||
|
{/* Logo */}
|
||||||
|
<Link href="/" className="flex items-center gap-[11px] px-2 pb-[22px] pt-1">
|
||||||
|
<div className="flex h-[38px] w-[38px] flex-none items-center justify-center rounded-[12px] bg-[linear-gradient(155deg,#F8C3A8,#F2937A)]">
|
||||||
|
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="4" />
|
||||||
|
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-display text-[17px] font-semibold leading-none text-[#3F362E]">OpenDayCare</div>
|
||||||
|
<div className="mt-[2px] text-[11.5px] text-[#A89A8B]">Sala Soles</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Nueva publicación */}
|
||||||
|
<Link
|
||||||
|
href="/crear-publicacion"
|
||||||
|
className="mb-[18px] flex w-full items-center justify-center gap-2 rounded-[14px] bg-[linear-gradient(180deg,#F4977E,#EE8164)] py-3 text-[14.5px] font-extrabold text-white shadow-[0_8px_18px_-8px_rgba(238,129,100,.75)]"
|
||||||
|
>
|
||||||
|
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M12 5v14M5 12h14" />
|
||||||
|
</svg>
|
||||||
|
Nueva publicación
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Navegación */}
|
||||||
|
<nav className="flex flex-1 flex-col gap-1">
|
||||||
|
{NAV_ITEMS.map((item) => {
|
||||||
|
const active = item.href === activeHref;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={`flex items-center gap-3 rounded-xl px-3 py-[11px] text-[14.5px] ${
|
||||||
|
active
|
||||||
|
? "bg-[#FBE3D8] font-extrabold text-[#D9583C]"
|
||||||
|
: "bg-transparent font-semibold text-[#6E6359]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Usuario */}
|
||||||
|
<div className="mt-[10px] border-t border-[#ECE0D0] pt-[14px]">
|
||||||
|
<div className="flex items-center gap-[11px] px-2 py-[6px]">
|
||||||
|
<div className="flex h-[38px] w-[38px] flex-none items-center justify-center rounded-full bg-[#F2937A] font-display text-base font-semibold text-white">
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="text-sm font-extrabold text-[#3F362E]">Caro Giménez</div>
|
||||||
|
<div className="text-xs text-[#A89A8B]">Maestra · Soles</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
title="Cerrar sesión"
|
||||||
|
className="flex h-8 w-8 flex-none items-center justify-center rounded-[10px] bg-[#F6ECDF] text-[#94887B]"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
export type PostCategory = "logro" | "actividad" | "anuncio";
|
||||||
|
|
||||||
|
export interface Post {
|
||||||
|
id: string;
|
||||||
|
authorName: string;
|
||||||
|
/** Inicial para el avatar circular (solo posts de niño; el anuncio usa ícono). */
|
||||||
|
authorInitial: string;
|
||||||
|
/** Colores del avatar, exactos a la plantilla. */
|
||||||
|
authorColors: { bg: string; text: string };
|
||||||
|
timeLabel: string;
|
||||||
|
publishedBy: string;
|
||||||
|
/** Define el badge y sus colores vía mapa en PostCard. */
|
||||||
|
category: PostCategory;
|
||||||
|
/** Texto tras "Para: " (sin el prefijo). */
|
||||||
|
audience: string;
|
||||||
|
body: string;
|
||||||
|
/** Si existe, se muestra el placeholder dashed de foto con este rótulo. */
|
||||||
|
photoLabel?: string;
|
||||||
|
likes: number;
|
||||||
|
comments: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const posts: Post[] = [
|
||||||
|
{
|
||||||
|
id: "mateo-logro",
|
||||||
|
authorName: "Mateo",
|
||||||
|
authorInitial: "M",
|
||||||
|
authorColors: { bg: "#A9D9E8", text: "#1F7A93" },
|
||||||
|
timeLabel: "14:20",
|
||||||
|
publishedBy: "publicado por vos",
|
||||||
|
category: "logro",
|
||||||
|
audience: "familia de Mateo",
|
||||||
|
body: "¡Usó el orinal solito por primera vez! Estaba feliz de contárselo a todos. Un gran paso.",
|
||||||
|
likes: 3,
|
||||||
|
comments: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mateo-actividad",
|
||||||
|
authorName: "Mateo",
|
||||||
|
authorInitial: "M",
|
||||||
|
authorColors: { bg: "#A9D9E8", text: "#1F7A93" },
|
||||||
|
timeLabel: "09:40",
|
||||||
|
publishedBy: "publicado por vos",
|
||||||
|
category: "actividad",
|
||||||
|
audience: "familia de Mateo",
|
||||||
|
body: "Pintamos con témperas esta mañana. Mateo eligió el azul para todo y se concentró un montón mezclando colores.",
|
||||||
|
photoLabel: "pintando con témperas",
|
||||||
|
likes: 5,
|
||||||
|
comments: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "anuncio-parque",
|
||||||
|
authorName: "Anuncio general",
|
||||||
|
authorInitial: "",
|
||||||
|
authorColors: { bg: "#CCD8F4", text: "#4E72C8" },
|
||||||
|
timeLabel: "07:50",
|
||||||
|
publishedBy: "publicado por vos",
|
||||||
|
category: "anuncio",
|
||||||
|
audience: "toda la sala",
|
||||||
|
body: "El viernes salimos al parque por la mañana. Recuerden mandar gorra y una botellita de agua.",
|
||||||
|
likes: 8,
|
||||||
|
comments: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
+19
-19
@@ -1,26 +1,26 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--font-sans: var(--font-nunito), system-ui, sans-serif;
|
||||||
--color-foreground: var(--foreground);
|
--font-display: var(--font-fredoka), system-ui, sans-serif;
|
||||||
--font-sans: var(--font-geist-sans);
|
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tema claro fijo: la plantilla no define modo oscuro */
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: #F6ECDF;
|
||||||
color: var(--foreground);
|
color: #3F362E;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: var(--font-nunito), system-ui, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar estilo plantilla */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #E4D6C4;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -1,27 +1,27 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Fredoka, Nunito } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const fredoka = Fredoka({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-fredoka",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const nunito = Nunito({
|
||||||
variable: "--font-geist-mono",
|
variable: "--font-nunito",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "OpenDayCare",
|
||||||
description: "Generated by create next app",
|
description: "Feed de la guardería OpenDayCare · Sala Soles",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }: LayoutProps<"/">) {
|
export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="es"
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
className={`${fredoka.variable} ${nunito.variable} h-full antialiased`}
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
<body className="min-h-full flex flex-col">{children}</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+24
-61
@@ -1,67 +1,30 @@
|
|||||||
import Image from "next/image";
|
import FeedHeader from "@/app/components/FeedHeader";
|
||||||
|
import PostCard from "@/app/components/PostCard";
|
||||||
|
import ShareBox from "@/app/components/ShareBox";
|
||||||
|
import Sidebar from "@/app/components/Sidebar";
|
||||||
|
import { posts } from "@/app/data/feed";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div className="flex min-h-screen bg-[#F6ECDF]">
|
||||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
<Sidebar activeHref="/" />
|
||||||
<Image
|
<main className="h-screen min-w-0 flex-1 overflow-y-auto">
|
||||||
className="dark:invert h-5 w-[100px]"
|
<div className="mx-auto w-full max-w-[760px] px-10 pb-20 pt-[34px]">
|
||||||
src="/next.svg"
|
<FeedHeader />
|
||||||
alt="Next.js logo"
|
<ShareBox />
|
||||||
width={100}
|
|
||||||
height={20}
|
<div className="mb-[14px] flex items-center gap-[14px]">
|
||||||
priority
|
<span className="text-[12.5px] font-extrabold tracking-[.8px] text-[#8A7C6D]">
|
||||||
/>
|
PUBLICADO HOY
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
</span>
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
<span className="h-px flex-1 bg-[#E7DAC8]" />
|
||||||
To get started, edit the{" "}
|
</div>
|
||||||
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
|
|
||||||
page.tsx
|
<div className="flex flex-col gap-4">
|
||||||
</code>{" "}
|
{posts.map((post) => (
|
||||||
file.
|
<PostCard key={post.id} post={post} />
|
||||||
</h1>
|
))}
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
</div>
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert h-[14px] w-4"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={14}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# spec workflow configuration
|
||||||
|
#
|
||||||
|
# AutoCreateBranch — controls whether /spec-impl creates the git branch automatically.
|
||||||
|
# true (default) → /spec-impl creates and switches to spec-NN-slug without asking
|
||||||
|
# false → /spec-impl asks for [y/N] confirmation before creating the branch
|
||||||
|
AutoCreateBranch: true
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# SPEC 01 — Home: feed de la guardería (plantilla feed.dc.html)
|
||||||
|
|
||||||
|
> **Estado:** Implementada
|
||||||
|
> **Depende de:** —
|
||||||
|
> **Fecha:** 2026-10-19
|
||||||
|
> **Objetivo:** Implementar la plantilla `references/pantallas/feed.dc.html` como página home (`/`) en Next.js, visualmente idéntica al original, sin autenticación ni base de datos.
|
||||||
|
|
||||||
|
## Alcance
|
||||||
|
|
||||||
|
**In:**
|
||||||
|
|
||||||
|
- Página `/` (`app/page.tsx`) con: sidebar fijo de 248px (logo OpenDayCare · Sala Soles, botón "Nueva publicación", nav con Feed activo + Niños/Avisos/Mi cuenta, tarjeta de usuario "Caro Giménez" con logout), saludo ("GUARDERÍA · SALA SOLES" / "Buenas, Caro" / "12 niños · martes 17 jun"), caja "Compartí un momento…", separador "PUBLICADO HOY" y los 3 posts de la plantilla (logro, actividad con placeholder de foto, anuncio).
|
||||||
|
- Datos mock tipados en `app/data/feed.ts` con los 3 posts exactos de la plantilla.
|
||||||
|
- Componentes en `app/components/`: `Sidebar.tsx`, `FeedHeader.tsx`, `ShareBox.tsx`, `PostCard.tsx`.
|
||||||
|
- Cambio de fuentes en `app/layout.tsx` (Fredoka + Nunito vía `next/font/google`), `lang="es"` y metadata "OpenDayCare".
|
||||||
|
- Estilos base en `app/globals.css` (fondo #F6ECDF, texto #3F362E, scrollbar custom, tema claro fijo).
|
||||||
|
- Todos los links como `<Link>` a rutas futuras (`/crear-publicacion`, `/ninos`, `/avisos`, `/mi-cuenta`, `/detalle-publicacion`, `/login`).
|
||||||
|
|
||||||
|
**Out of scope (para specs futuras):**
|
||||||
|
|
||||||
|
- Las páginas destino del nav y sus funcionalidades (hoy darán 404 y está bien).
|
||||||
|
- Autenticación y logout funcional.
|
||||||
|
- Base de datos y persistencia.
|
||||||
|
- Interactividad real de likes, comentarios y editar (solo visual, contadores fijos).
|
||||||
|
- Responsive móvil.
|
||||||
|
- Dark mode.
|
||||||
|
- Foto real en el post de actividad (se mantiene el placeholder dashed de la plantilla).
|
||||||
|
|
||||||
|
## Modelo de datos
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// app/data/feed.ts
|
||||||
|
export type PostCategory = "logro" | "actividad" | "anuncio";
|
||||||
|
|
||||||
|
export interface Post {
|
||||||
|
id: string;
|
||||||
|
authorName: string; // "Mateo" | "Anuncio general"
|
||||||
|
authorInitial: string; // "M" (solo posts de niño)
|
||||||
|
authorColors: { bg: string; text: string }; // p.ej. { bg: "#A9D9E8", text: "#1F7A93" }
|
||||||
|
timeLabel: string; // "14:20"
|
||||||
|
publishedBy: string; // "publicado por vos"
|
||||||
|
category: PostCategory; // define badge y colores vía mapa en PostCard
|
||||||
|
audience: string; // "familia de Mateo" | "toda la sala"
|
||||||
|
body: string;
|
||||||
|
photoLabel?: string; // "pintando con témperas" → placeholder dashed
|
||||||
|
likes: number;
|
||||||
|
comments: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const posts: Post[] = [
|
||||||
|
// Los 3 posts de la plantilla: logro (3/1), actividad con foto (5/2), anuncio (8/0)
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Convenciones: los colores van como hex exactos de la plantilla; el mapa `category → colores del badge` vive en `PostCard.tsx`.
|
||||||
|
|
||||||
|
## Plan de implementación
|
||||||
|
|
||||||
|
1. **Fuentes y layout** — `app/layout.tsx`: reemplazar Geist por Fredoka y Nunito como fuentes variables con `next/font/google` (variables `--font-fredoka` / `--font-nunito`), `lang="es"`, title "OpenDayCare". Test manual: `npm run dev` sigue compilando.
|
||||||
|
2. **Estilos globales** — `app/globals.css`: body con `background:#F6ECDF`, `color:#3F362E`, `font-family: var(--font-nunito), system-ui, sans-serif`; scrollbar custom (thumb `#E4D6C4`); eliminar el bloque `prefers-color-scheme` y las variables Geist. Test manual: la app ya se ve crema.
|
||||||
|
3. **Mock del feed** — crear `app/data/feed.ts` con el tipo `Post` y los 3 posts de la plantilla.
|
||||||
|
4. **Sidebar** — crear `app/components/Sidebar.tsx` con logo, botón "Nueva publicación" (→ `/crear-publicacion`), nav (Feed activo, Niños, Avisos, Mi cuenta) y usuario + logout (→ `/login`).
|
||||||
|
5. **Componentes del feed** — crear `FeedHeader.tsx`, `ShareBox.tsx` y `PostCard.tsx` con utilities Tailwind de valores exactos.
|
||||||
|
6. **Página home** — reemplazar `app/page.tsx`: flex con Sidebar + `<main>` de scroll propio, contenido centrado `max-width:760px`, mapeando `posts`. Test manual: `http://localhost:3000` muestra el feed completo.
|
||||||
|
7. **Fidelidad visual** — comparación lado a lado con `references/pantallas/feed.dc.html` a ancho ≥1280px; ajustar diferencias. Captura de verificación en `.playwright-mcp/`.
|
||||||
|
|
||||||
|
## Criterios de aceptación
|
||||||
|
|
||||||
|
- [ ] `npm run dev` levanta sin errores y `/` muestra el feed completo: sidebar + saludo + caja + separador + 3 posts.
|
||||||
|
- [ ] `npm run build` completa sin errores.
|
||||||
|
- [ ] A ancho ≥1280px el layout coincide con la plantilla: sidebar 248px sticky, main con scroll propio y contenido centrado de 760px.
|
||||||
|
- [ ] Fredoka en logo, saludo y nombres de autor; Nunito (pesos 400–800) en el resto.
|
||||||
|
- [ ] Post 1: badge LOGRO (fondo `#CFEBD8`, texto `#3E9B6C`), ♥ 3, 💬 1, link "Editar".
|
||||||
|
- [ ] Post 2: badge ACTIVIDAD (fondo `#C7E7F1`, texto `#2E89A6`), placeholder dashed "Foto · pintando con témperas", ♥ 5, 💬 2.
|
||||||
|
- [ ] Post 3: avatar megáfono (fondo `#CCD8F4`), badge ANUNCIO, "Para: toda la sala", ♥ 8, 💬 0.
|
||||||
|
- [ ] Todos los links navegan con `<Link>`: `/crear-publicacion` (botón, caja y Editar), `/ninos`, `/avisos`, `/mi-cuenta`, `/detalle-publicacion` (íconos de comentarios), `/login` (logout).
|
||||||
|
- [ ] Colores base exactos: fondo `#F6ECDF`, tarjetas `#FFFDF9` con borde `#ECE0D0`, sombras y radios según plantilla.
|
||||||
|
- [ ] Con el sistema en modo oscuro la página se mantiene en tema claro (sin dark mode).
|
||||||
|
- [ ] Los componentes nuevos no usan atributos `style`; todo está en utilities Tailwind con valores arbitrarios exactos.
|
||||||
|
|
||||||
|
## Decisiones
|
||||||
|
|
||||||
|
- **Sí:** Tailwind 4 con valores arbitrarios exactos (`bg-[#F6ECDF]`, `shadow-[0_4px_16px_-12px_rgba(120,90,60,.5)]`…) — consistente con el proyecto y pixel-perfect.
|
||||||
|
- **No:** estilos inline literales ni CSS Modules — divergen de la convención del repo.
|
||||||
|
- **Sí:** componentes separados en `app/components/` — el sidebar se reutiliza en Niños, Avisos y Mi cuenta.
|
||||||
|
- **Sí:** mock tipado en `app/data/feed.ts` — deja el terreno listo para la BD futura.
|
||||||
|
- **Sí:** `<Link>` a rutas futuras aunque hoy den 404 — la navegación queda cableada desde el día uno.
|
||||||
|
- **Sí:** solo desktop, réplica exacta — el móvil merece decisiones de diseño propias (otra spec).
|
||||||
|
- **Sí:** `next/font/google` para Fredoka y Nunito como fuentes variables — misma familia y pesos que el `<link>` de la plantilla, self-hosted y sin FOUT.
|
||||||
|
- **Sí:** tema claro fijo, eliminando el dark mode automático de `globals.css` — la plantilla no define modo oscuro.
|
||||||
|
- **No:** autenticación, BD, likes/comentarios/editar funcionales ni foto real — excluidos explícitamente por el usuario.
|
||||||
|
|
||||||
|
## Qué **no** está en esta spec
|
||||||
|
|
||||||
|
- Otras páginas (Niños, Avisos, Mi cuenta, crear/editar publicación, detalle, login) — cada una irá en su propia spec.
|
||||||
|
- Autenticación y base de datos.
|
||||||
|
- Likes, comentarios y edición funcionales.
|
||||||
|
- Responsive móvil y dark mode.
|
||||||
Reference in New Issue
Block a user