/** * ExtAppPostCard - Render posts from external apps * * Supports two rendering modes: * - Level 1: JSON config (uses CardRenderer) * - Level 3: Pre-rendered HTML from sandbox (uses dangerouslySetInnerHTML) */ import React, { useMemo } from "react"; import CardRenderer from "../Cards/CardRenderer"; import { buildAssetUrl } from "../../api/extAppsApi"; // Default theme tokens for Level 1 cards const DEFAULT_TOKENS = { colors: { surface: "#1a1a1a", primary: "#8b5cf6", text: "#ffffff", textSecondary: "#a1a1aa", accent: "#22c55e", }, typography: { title: { fontSize: 16, fontWeight: 600, color: "#ffffff" }, subtitle: { fontSize: 14, fontWeight: 400, color: "#a1a1aa" }, body: { fontSize: 13, color: "#d4d4d8" }, caption: { fontSize: 11, color: "#71717a" }, price: { fontSize: 18, fontWeight: 700, color: "#22c55e" }, }, chips: { default: { background: "#27272a", color: "#d4d4d8", borderRadius: 12 }, primary: { background: "#8b5cf6", color: "#ffffff", borderRadius: 12 }, success: { background: "#166534", color: "#86efac", borderRadius: 12 }, }, buttons: { primary: { background: "#8b5cf6", color: "#ffffff", borderRadius: 8 }, secondary: { background: "#27272a", color: "#d4d4d8", borderRadius: 8 }, }, radius: { card: 16 }, shadow: { card: "0 4px 12px rgba(0,0,0,0.3)" }, }; /** * Process image URLs in post data to use asset proxy */ function processAssetUrls(post, appId) { const processed = { ...post }; const processUrl = (url) => { if (!url || typeof url !== "string") return url; // If it's an app-relative path (not http) if (!url.startsWith("http") && !url.startsWith("/api/assets")) { return buildAssetUrl(appId, url, { w: 400 }); } return url; }; if (processed.image) processed.image = processUrl(processed.image); if (processed.image_small) processed.image_small = processUrl(processed.image_small); if (processed.image_medium) processed.image_medium = processUrl(processed.image_medium); if (processed.thumbnail) processed.thumbnail = processUrl(processed.thumbnail); return processed; } /** * Default card spec for apps without custom card_style */ const DEFAULT_CARD_SPEC = { size: { w: 280, h: 180 }, radius: 16, background: { type: "image", bind: "data.image" }, layers: [ { id: "gradient", type: "rect", x: 0, y: 80, w: 280, h: 100, style: "gradients.bottom", }, { id: "title", type: "text", x: 12, y: 120, w: 256, bind: "data.title", style: "typography.title", maxLines: 2, }, { id: "source", type: "text", x: 12, y: 155, w: 200, bind: "data.source_name", style: "typography.caption", }, ], }; export default function ExtAppPostCard({ post, appId, cardStyle, onAction, className = "", }) { // Process asset URLs const processedPost = useMemo( () => processAssetUrls(post, appId), [post, appId] ); // If post has pre-rendered content from Level 3 sandbox if (post.rendered && post.rendered.html) { return (