rnxcmkidd: Add the viewport-fit bento shell and a dev feed preview with fixtures

This commit is contained in:
2026-10-19 22:08:21 -06:00
parent 9aeb9a379f
commit 89064e88c1
15 changed files with 638 additions and 120 deletions
+182
View File
@@ -0,0 +1,182 @@
// Fixture data for the dev-only preview routes (/dev/feed). Nothing here is
// real: no Minnect person, post, token or URL. Images are inline SVG data URIs
// so the preview never calls the network. Later tasks extend this file.
import type { Person } from "@/lib/auth-client";
import type { DiscoverCategory, DiscoverData, DiscoverExpert, DiscoverMinnect } from "@/lib/discover";
import type { ExpertResult } from "@/lib/experts-search";
/** Fixed "now" so relative times in the preview are stable. */
export const FIXTURE_NOW = Date.parse("2026-10-19T12:00:00Z");
/** A 16:9 gradient placeholder image, drawn with hsl() so no fetch happens. */
export function placeholderImage(hue: number, label = ""): string {
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">` +
`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">` +
`<stop offset="0" stop-color="hsl(${hue} 45% 32%)"/><stop offset="1" stop-color="hsl(${(hue + 40) % 360} 40% 14%)"/>` +
`</linearGradient></defs><rect width="1280" height="720" fill="url(#g)"/>` +
(label
? `<text x="64" y="660" font-family="system-ui,sans-serif" font-size="56" font-weight="700" fill="hsl(0 0% 100% / .85)">${label}</text>`
: "") +
`</svg>`;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}
export const FIXTURE_FEED_USER_ID = "dev-user-1";
export const FIXTURE_PERSON: Person = {
name: "Jordan Rivera",
email: "jordan.rivera@example.com",
pictureUrl: null,
feedUserId: FIXTURE_FEED_USER_ID,
};
const author = (id: string, displayName: string, picture: string | null = null) => ({
id,
displayName,
profilePictureUrl: picture,
});
/** Raw feed items in the shape /api/feed returns; run them through toCardPosts. */
export const FIXTURE_FEED_ITEMS: unknown[] = [
{
type: "post",
publishedAt: "2026-10-19T11:42:00Z",
likesCount: 4,
commentsCount: 1,
post: {
id: "p1",
content: "Shipped the new onboarding flow this morning. The best feedback so far came from a five minute call, not a survey.",
author: author(FIXTURE_FEED_USER_ID, "Jordan Rivera"),
mediaItems: [],
},
},
{
type: "post",
publishedAt: "2026-10-19T10:15:00Z",
likesCount: 128,
commentsCount: 23,
isLiked: true,
post: {
id: "p2",
content: "Three questions I ask before every pricing change:\n• Who pays more?\n• Who leaves?\n• What do we learn either way?",
author: author("a2", "Priya Natarajan"),
mediaItems: [{ mediaType: "image", imageUrl: placeholderImage(352, "Pricing review"), order: 0 }],
},
},
{
type: "post",
publishedAt: "2026-10-19T08:30:00Z",
likesCount: 56,
commentsCount: 7,
post: {
id: "p3",
content: "A short walkthrough of how we run weekly reviews with a remote team.",
author: author("a3", "Marcus Bell"),
mediaItems: [{ mediaType: "video", thumbnailUrl: placeholderImage(210, "Weekly reviews"), order: 0 }],
},
},
{
type: "post",
publishedAt: "2026-10-18T19:05:00Z",
likesCount: 9,
commentsCount: 0,
post: {
id: "p4",
content: "Reading list for anyone moving from engineering into product: start with the customer interviews, then the numbers.",
author: author("a4", "Elena Kovac"),
mediaItems: [],
},
},
{
type: "post",
publishedAt: "2026-10-18T14:40:00Z",
likesCount: 312,
commentsCount: 41,
post: {
id: "p5",
content: "Fundraising tip: send the update even when the month was bad. Especially when the month was bad.",
author: author("a5", "Samuel Okafor"),
mediaItems: [{ mediaType: "image", imageUrl: placeholderImage(30, "Monthly update"), order: 0 }],
},
},
{
type: "post",
publishedAt: "2026-10-17T09:00:00Z",
likesCount: 17,
commentsCount: 3,
post: {
id: "p6",
content: "Your first ten customers will tell you what the product is. Listen before you build the next feature.",
author: author("a6", "Hannah Lindqvist"),
mediaItems: [],
},
},
];
const expert = (id: string, displayName: string, byline: string, rating: number, reviewsCount: number): DiscoverExpert => ({
id,
slug: displayName.replace(/\s+/g, ""),
displayName,
byline,
pictureUrl: null,
rating,
reviewsCount,
available: true,
location: { city: null, country: null },
});
export const FIXTURE_FEATURED: DiscoverExpert[] = [
expert("e1", "Priya Natarajan", "Pricing and growth advisor", 4.9, 212),
expert("e2", "Marcus Bell", "Remote team coach", 4.8, 96),
expert("e3", "Elena Kovac", "Product leadership", 4.7, 58),
expert("e4", "Samuel Okafor", "Seed-stage fundraising", 5.0, 140),
expert("e5", "Hannah Lindqvist", "Customer research", 4.6, 33),
];
const minnect = (id: string, who: DiscoverExpert, question: string, views: number, hue: number): DiscoverMinnect => ({
id,
question,
views,
thumbnailUrl: placeholderImage(hue),
media: null,
expert: { slug: who.slug, displayName: who.displayName, pictureUrl: null, byline: who.byline, rating: who.rating },
});
export const FIXTURE_TRENDING: DiscoverMinnect[] = [
minnect("m1", FIXTURE_FEATURED[0], "How do I raise prices without losing my best customers?", 12840, 352),
minnect("m2", FIXTURE_FEATURED[3], "What should a pre-seed deck show in the first three slides?", 9310, 30),
minnect("m3", FIXTURE_FEATURED[1], "How often should a remote team meet in person?", 6120, 210),
minnect("m4", FIXTURE_FEATURED[2], "When is the right time to hire a first product manager?", 4470, 270),
minnect("m5", FIXTURE_FEATURED[4], "How many customer interviews are enough before building?", 2015, 150),
];
export const FIXTURE_CATEGORIES: DiscoverCategory[] = [
{ id: 1, name: "Business", icon: null, sortOrder: 1 },
{ id: 2, name: "Coaching", icon: null, sortOrder: 2 },
{ id: 3, name: "Finance", icon: null, sortOrder: 3 },
{ id: 4, name: "Product", icon: null, sortOrder: 4 },
{ id: 5, name: "Media", icon: null, sortOrder: 5 },
];
export const FIXTURE_DISCOVER: DiscoverData = {
featuredExperts: FIXTURE_FEATURED,
trendingMinnects: FIXTURE_TRENDING,
categories: FIXTURE_CATEGORIES,
};
/** Find experts results: every featured expert plus a few more, filterable by name. */
export const FIXTURE_SEARCH_RESULTS: ExpertResult[] = [
...FIXTURE_FEATURED,
expert("e6", "Daniel Moreau", "Sales operations", 4.5, 21),
expert("e7", "Aiko Tanaka", "Brand strategy", 4.8, 77),
expert("e8", "Rafael Duarte", "Engineering management", 4.4, 18),
].map((e) => ({
id: e.id,
slug: e.slug,
displayName: e.displayName,
byline: e.byline,
pictureUrl: e.pictureUrl,
rating: e.rating,
categories: [],
}));