183 lines
4.9 KiB
JavaScript
183 lines
4.9 KiB
JavaScript
/**
|
|
* 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 (
|
|
<div
|
|
className={`ext-app-card ext-app-card--rendered ${className}`}
|
|
style={{
|
|
width: post.rendered.width || 280,
|
|
height: post.rendered.height || 180,
|
|
borderRadius: 16,
|
|
overflow: "hidden",
|
|
boxShadow: DEFAULT_TOKENS.shadow.card,
|
|
}}
|
|
dangerouslySetInnerHTML={{ __html: post.rendered.html }}
|
|
onClick={() => onAction?.({ type: "open", bind: "post_id" }, post.post_id)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// Level 1: Use CardRenderer with JSON spec
|
|
const spec = cardStyle || DEFAULT_CARD_SPEC;
|
|
|
|
return (
|
|
<CardRenderer
|
|
spec={spec}
|
|
data={processedPost}
|
|
themeTokens={{
|
|
...DEFAULT_TOKENS,
|
|
gradients: {
|
|
bottom: "linear-gradient(to bottom, transparent, rgba(0,0,0,0.8))",
|
|
},
|
|
}}
|
|
onAction={(action, value) => onAction?.(action, value)}
|
|
className={`ext-app-card ${className}`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Small version of the card for map markers
|
|
*/
|
|
export function ExtAppPostMarker({ post, appId, onClick }) {
|
|
const imageUrl = post.image_small || post.thumbnail || post.image;
|
|
const processedUrl = imageUrl && !imageUrl.startsWith("http")
|
|
? buildAssetUrl(appId, imageUrl, { w: 80, h: 80 })
|
|
: imageUrl;
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className="w-12 h-12 rounded-full bg-zinc-800 border-2 border-purple-500 overflow-hidden shadow-lg hover:scale-110 transition-transform"
|
|
style={
|
|
processedUrl
|
|
? {
|
|
backgroundImage: `url(${processedUrl})`,
|
|
backgroundSize: "cover",
|
|
backgroundPosition: "center",
|
|
}
|
|
: {}
|
|
}
|
|
>
|
|
{!processedUrl && (
|
|
<i className="fas fa-map-marker-alt text-purple-400" />
|
|
)}
|
|
</button>
|
|
);
|
|
}
|