diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index a0ca1ca..2e772a8 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -76,6 +76,11 @@ export default function MapView({ CREATE_CATEGORY_MAP.News[0] ); const [draftCoords, setDraftCoords] = useState(null); + const [draftImage, setDraftImage] = useState(""); + const [draftImageFile, setDraftImageFile] = useState(null); + const [draftImagePreview, setDraftImagePreview] = useState(""); + const [useCustomImage, setUseCustomImage] = useState(false); + const [uploadingImage, setUploadingImage] = useState(false); const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(""); @@ -219,6 +224,10 @@ export default function MapView({ setIsCreating(true); setDraftTitle(""); setDraftBody(""); + setDraftImage(""); + setDraftImageFile(null); + setDraftImagePreview(""); + setUseCustomImage(false); const main = mainFilter === "All" ? "News" : mainFilter; setDraftCategory(main); const list = CREATE_CATEGORY_MAP[main] || CREATE_CATEGORY_MAP.News; @@ -234,6 +243,62 @@ export default function MapView({ setSearchQuery(""); }; + const handleImageFileChange = async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Vérifier le type de fichier + if (!file.type.startsWith('image/')) { + setSaveError("Please select an image file"); + return; + } + + // Vérifier la taille (max 5MB) + if (file.size > 5 * 1024 * 1024) { + setSaveError("Image must be less than 5MB"); + return; + } + + setDraftImageFile(file); + + // Créer preview + const reader = new FileReader(); + reader.onloadend = () => { + setDraftImagePreview(reader.result); + }; + reader.readAsDataURL(file); + + // Upload immédiatement + setUploadingImage(true); + setSaveError(""); + + try { + const formData = new FormData(); + formData.append('image', file); + + const res = await fetch('/api/upload-image', { + method: 'POST', + body: formData, + credentials: 'include', + headers: token ? { 'Authorization': `Bearer ${token}` } : {}, + }); + + if (!res.ok) { + throw new Error('Upload failed'); + } + + const data = await res.json(); + setDraftImage(data.url || data.image_url); + setUploadingImage(false); + } catch (err) { + console.error('Image upload error:', err); + setSaveError('Failed to upload image'); + setUploadingImage(false); + setDraftImageFile(null); + setDraftImagePreview(""); + } + }; + const handleSubmitPost = async () => { if (isSaving) return; setSaveError(""); @@ -282,19 +347,23 @@ export default function MapView({ try { setIsSaving(true); - const newPost = await createPost( - { - title: draftTitle.trim(), - snippet: draftBody.trim() || draftTitle.trim(), - category: - draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(), - sub_category: draftSubCategory || "ALL", - lat, - lon: lng, - author: authorName, - }, - token - ); + const payload = { + title: draftTitle.trim(), + snippet: draftBody.trim() || draftTitle.trim(), + category: + draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(), + sub_category: draftSubCategory || "ALL", + lat, + lon: lng, + author: authorName, + }; + + if (useCustomImage && draftImage.trim()) { + payload.image_small = draftImage.trim(); + payload.image_large = draftImage.trim(); + } + + const newPost = await createPost(payload, token); if (newPost && newPost.id) handleIncomingPost(newPost); @@ -510,6 +579,68 @@ export default function MapView({ onChange={(e) => setDraftBody(e.target.value)} /> +
+ +
+ + {useCustomImage && ( +
+
+ + {uploadingImage && Uploading...} +
+ + {draftImagePreview && ( +
+ Preview + +
+ )} + +
+ Or paste an image URL: +
+ { + setDraftImage(e.target.value); + setDraftImageFile(null); + setDraftImagePreview(""); + }} + disabled={!!draftImageFile} + /> +
+ )} + {saveError &&
{saveError}
}
diff --git a/src/components/Map/templateSpecs.js b/src/components/Map/templateSpecs.js index 48d4908..e8ef9fd 100644 --- a/src/components/Map/templateSpecs.js +++ b/src/components/Map/templateSpecs.js @@ -15,9 +15,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "newsBlue" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "NEWS", style: "chip.news" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf1ea", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "NEWS", style: "chip.news" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -38,9 +40,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "breakingRed" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "BREAKING", style: "chip.danger" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf0e7", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "BREAKING", style: "chip.danger" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -61,9 +65,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "financeTeal" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "FINANCE", style: "chip.news" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf201", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "FINANCE", style: "chip.news" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -84,9 +90,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "marketGold" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "MARKET", style: "chip.news" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf3d1", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "MARKET", style: "chip.news" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -107,9 +115,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "friendsGreen" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "FRIENDS", style: "chip.news" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf500", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "FRIENDS", style: "chip.news" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -130,9 +140,11 @@ export const TEMPLATE_SPECS = { background: { type: "gradient", value: "eventsPurple" }, radius: 18, layers: [ - { id: "badge", type: "chip", x: 12, y: 10, text: "EVENTS", style: "chip.news" }, - { id: "title", type: "text", x: 12, y: 34, w: 216, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, - { id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, + { id: "icon", type: "icon", x: 12, y: 12, w: 72, h: 72, text: "\uf073", style: "text.icon", bind: "data.categoryIcon" }, + { id: "imageOverlay", type: "image", x: 12, y: 12, w: 72, h: 72, bind: "data.image", radius: 12 }, + { id: "badge", type: "chip", x: 94, y: 10, text: "EVENTS", style: "chip.news" }, + { id: "title", type: "text", x: 94, y: 34, w: 134, h: 42, bind: "data.headline", style: "text.title", maxLines: 2 }, + { id: "meta", type: "text", x: 94, y: 76, w: 134, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }, ], }, full_spec: { @@ -195,6 +207,58 @@ export function getTemplateSpecForPost(post, variant /* "mini"|"full" */) { return variant === "full" ? t.full_spec : t.mini_spec; } +function getCategoryIcon(post) { + const category = (post?.category || "").toUpperCase(); + const subCategory = (post?.sub_category || "").toLowerCase(); + + // Mapping des icônes par catégorie et sous-catégorie + const iconMap = { + NEWS: { + default: "\uf1ea", // fa-newspaper + world: "\uf57d", // fa-globe-americas + politics: "\uf0a3", // fa-landmark-flag + tech: "\uf2db", // fa-microchip + finance: "\uf201", // fa-chart-line + local: "\uf3c5", // fa-map-marker-alt + }, + FRIENDS: { + default: "\uf500", // fa-user-group + nearby: "\uf3c5", // fa-location-crosshairs + chats: "\uf086", // fa-comments + groups: "\uf0c0", // fa-users + stories: "\uf02d", // fa-book-open + favs: "\uf004", // fa-heart + }, + EVENT: { + default: "\uf073", // fa-calendar + today: "\uf783", // fa-calendar-day + weekend: "\uf784", // fa-calendar-week + music: "\uf001", // fa-music + sports: "\uf1e3", // fa-football + local: "\uf3c5", // fa-map-marker-alt + }, + EVENTS: { + default: "\uf073", // fa-calendar + today: "\uf783", // fa-calendar-day + weekend: "\uf784", // fa-calendar-week + music: "\uf001", // fa-music + sports: "\uf1e3", // fa-football + local: "\uf3c5", // fa-map-marker-alt + }, + MARKET: { + default: "\uf3d1", // fa-store + actu: "\uf1ea", // fa-newspaper + tech: "\uf2db", // fa-microchip + finan: "\uf555", // fa-money-bill-trend-up + sport: "\uf434", // fa-basketball + deals: "\uf02b", // fa-tags + }, + }; + + const categoryIcons = iconMap[category] || iconMap.NEWS; + return categoryIcons[subCategory] || categoryIcons.default; +} + export function adaptPostToTemplateData(post) { const headline = post?.title || "Untitled"; const author = post?.author ? `by ${post.author}` : ""; @@ -203,5 +267,6 @@ export function adaptPostToTemplateData(post) { const summary = post?.snippet || post?.body || ""; const url = post?.url || ""; const image = post?.image_large || post?.image_small || post?.image || post?.media_url || ""; - return { headline, source, summary, url, image }; + const categoryIcon = getCategoryIcon(post); + return { headline, source, summary, url, image, categoryIcon }; } diff --git a/src/theme/cardTokens.js b/src/theme/cardTokens.js index 66f152e..0de6645 100644 --- a/src/theme/cardTokens.js +++ b/src/theme/cardTokens.js @@ -22,6 +22,7 @@ export const cardTokens = { meta: { color: "#A9B3C7", fontSize: 12, fontWeight: 600 }, h1: { color: "#E9EEF8", fontSize: 22, fontWeight: 900, lineHeight: "26px" }, body: { color: "#D7DEED", fontSize: 14, fontWeight: 600, lineHeight: "18px" }, + icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 }, }, chip: { news: { background: "rgba(78,161,255,0.18)", color: "#CFE6FF", fontWeight: 800, fontSize: 12, borderRadius: 999 }, @@ -55,6 +56,7 @@ export const cardTokens = { meta: { color: "#B5C7E6", fontSize: 12, fontWeight: 700 }, h1: { color: "#F0F7FF", fontSize: 22, fontWeight: 900, lineHeight: "26px" }, body: { color: "#D7E9FF", fontSize: 14, fontWeight: 700, lineHeight: "18px" }, + icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 }, }, chip: { news: { background: "rgba(56,189,248,0.18)", color: "#D7F3FF", fontWeight: 900, fontSize: 12, borderRadius: 999 }, @@ -88,6 +90,7 @@ export const cardTokens = { meta: { color: "#5B677A", fontSize: 12, fontWeight: 700 }, h1: { color: "#0B1220", fontSize: 22, fontWeight: 900, lineHeight: "26px" }, body: { color: "#1A2740", fontSize: 14, fontWeight: 600, lineHeight: "18px" }, + icon: { color: "rgba(0,0,0,0.15)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 }, }, chip: { news: { background: "rgba(30,107,255,0.12)", color: "#0B1220", fontWeight: 900, fontSize: 12, borderRadius: 999 },