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 && (
+
+

+
+
+ )}
+
+
+ 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 },