diff --git a/package.json b/package.json index 9962703..a086af3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sociowire-frontend", "private": true, - "version": "0.0.49", + "version": "0.0.50", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", diff --git a/src/components/Layout/TopBarNew.jsx b/src/components/Layout/TopBarNew.jsx index b0b4f29..584442d 100644 --- a/src/components/Layout/TopBarNew.jsx +++ b/src/components/Layout/TopBarNew.jsx @@ -473,107 +473,136 @@ export default function TopBarNew({ theme = "dark", onChangeTheme, onIslandsClic )} - {/* Header - Premium Social Network Style */} + {/* Header - Hot Social Network Style */} - {/* Clean dark background */} -
- {/* Subtle bottom border */} -
+ {/* Glassmorphism background with gradient */} +
+ {/* Animated accent line */} + -
- {/* Left: Clean Logo */} -
-
-
- SocioWire +
+ {/* Left: Logo with pulse effect */} + +
+ +
+
+ S +
-
- sociowire - - +
+ + SOCIOWIRE + + + {t('topbar.wiredToLife')}
-
+
- {/* Center: Spacer for balance */} -
- - {/* Right: Actions */} -
- {/* PWA Install */} - {showInstallBtn && ( - promptInstall()} - title={t('topbar.installApp')} - /> - )} - - {/* Theme - mobile */} -
+ {/* Center: Theme pills (desktop) */} +
+ {THEMES.map((themeKey) => ( onChangeTheme?.(themeKey)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > - + {t(`theme.${themeKey}`)} + + ))} +
+ + {/* Right: Action buttons */} +
+ {/* Install PWA */} + {showInstallBtn && ( + promptInstall()} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + + {t('topbar.install')} + + )} + + {/* Theme (mobile) */} +
+ +
{/* Language */}
- +
{/* Friends */} {authenticated && ( setShowFriends(!showFriends)} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} > - + {pendingRequestsCount > 0 && ( {pendingRequestsCount} @@ -581,8 +610,8 @@ export default function TopBarNew({ theme = "dark", onChangeTheme, onIslandsClic )} - {/* Divider */} -
+ {/* Separator */} +
{/* User/Auth */} {authenticated ? ( diff --git a/src/components/Posts/PostCreateModal.jsx b/src/components/Posts/PostCreateModal.jsx index f8147f6..401c0dd 100644 --- a/src/components/Posts/PostCreateModal.jsx +++ b/src/components/Posts/PostCreateModal.jsx @@ -1,7 +1,15 @@ import React, { useState, useRef, useCallback, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useTranslation } from "react-i18next"; -import { uploadImage } from "../../api/client"; +import { uploadImage, fetchLinkPreview } from "../../api/client"; + +// Content modes +const CONTENT_MODES = [ + { id: "text", icon: "fa-pen", label: "Text", color: "from-blue-500 to-cyan-500" }, + { id: "photo", icon: "fa-image", label: "Photo", color: "from-emerald-500 to-green-500" }, + { id: "link", icon: "fa-link", label: "Link", color: "from-purple-500 to-violet-500" }, + { id: "live", icon: "fa-broadcast-tower", label: "Live", color: "from-red-500 to-rose-500" }, +]; const CATEGORIES = [ { id: "News", icon: "fa-newspaper", color: "from-blue-500 to-cyan-500" }, @@ -21,27 +29,36 @@ export default function PostCreateModal({ isOpen, onClose, onSubmit, + onGoLive, coords, initialCategory = "News", isSaving = false, saveError = "", }) { const { t } = useTranslation(); + const [mode, setMode] = useState("text"); const [category, setCategory] = useState(initialCategory); const [subCategory, setSubCategory] = useState(SUB_CATEGORIES[initialCategory]?.[0] || ""); const [title, setTitle] = useState(""); const [body, setBody] = useState(""); + const [linkUrl, setLinkUrl] = useState(""); + const [linkPreview, setLinkPreview] = useState(null); + const [linkLoading, setLinkLoading] = useState(false); const [images, setImages] = useState([]); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const cameraInputRef = useRef(null); const textareaRef = useRef(null); + const linkTimeoutRef = useRef(null); // Reset on open useEffect(() => { if (isOpen) { + setMode("text"); setTitle(""); setBody(""); + setLinkUrl(""); + setLinkPreview(null); setImages([]); setCategory(initialCategory); setSubCategory(SUB_CATEGORIES[initialCategory]?.[0] || ""); @@ -52,10 +69,33 @@ export default function PostCreateModal({ useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = "auto"; - textareaRef.current.style.height = textareaRef.current.scrollHeight + "px"; + textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 200) + "px"; } }, [body]); + // Link preview fetch + useEffect(() => { + if (mode !== "link" || !linkUrl.trim()) { + setLinkPreview(null); + return; + } + + clearTimeout(linkTimeoutRef.current); + linkTimeoutRef.current = setTimeout(async () => { + try { + setLinkLoading(true); + const preview = await fetchLinkPreview(linkUrl.trim()); + if (preview) { + setLinkPreview(preview); + if (!title && preview.title) setTitle(preview.title); + } + } catch {} + setLinkLoading(false); + }, 500); + + return () => clearTimeout(linkTimeoutRef.current); + }, [linkUrl, mode, title]); + const handleFileChange = useCallback(async (e) => { const files = Array.from(e.target.files || []); if (files.length === 0) return; @@ -80,19 +120,28 @@ export default function PostCreateModal({ }; const handleSubmit = () => { - if (!title.trim() && !body.trim() && images.length === 0) return; + if (mode === "live") { + onGoLive?.({ category, subCategory, title, body, coords }); + return; + } + + if (!title.trim() && !body.trim() && images.length === 0 && !linkUrl.trim()) return; onSubmit?.({ + mode, title: title.trim(), body: body.trim(), category, subCategory, images, + linkUrl: linkUrl.trim(), + linkPreview, coords, }); }; - const canSubmit = (title.trim() || body.trim() || images.length > 0) && !isSaving && !uploading; + const canSubmit = mode === "live" || + (title.trim() || body.trim() || images.length > 0 || linkUrl.trim()) && !isSaving && !uploading; if (!isOpen) return null; @@ -106,70 +155,94 @@ export default function PostCreateModal({ > {/* Backdrop */} {/* Modal */} + {/* Glowing top border */} +
+ {/* Header */} -
+
- + -

Create Post

+

Create Post

{isSaving ? ( - <>Posting... + + ) : mode === "live" ? ( + <> + + Go Live + ) : ( "Post" )}
- {/* Content */} -
- {/* Category chips */} -
+ {/* Content modes */} +
+ {CONTENT_MODES.map((m) => ( + setMode(m.id)} + > + + {m.label} + + ))} +
+ + {/* Scrollable content */} +
+ {/* Category row */} +
{CATEGORIES.map((cat) => ( { setCategory(cat.id); setSubCategory(SUB_CATEGORIES[cat.id]?.[0] || ""); @@ -181,60 +254,122 @@ export default function PostCreateModal({ ))}
- {/* Sub-category chips */} -
+ {/* Sub-category */} +
{SUB_CATEGORIES[category]?.map((sub) => ( - setSubCategory(sub)} > {sub} - + ))}
+ {/* Link input (for link mode) */} + {mode === "link" && ( +
+
+ + setLinkUrl(e.target.value)} + className="w-full pl-10 pr-4 py-3 rounded-xl + bg-surface-700/50 border border-white/10 + text-surface-100 text-sm + placeholder:text-surface-500 + focus:outline-none focus:border-cyan-500/50" + /> + {linkLoading && ( + + )} +
+ {/* Link preview card */} + {linkPreview && ( + + {linkPreview.image && ( + + )} +
+

{linkPreview.site_name || new URL(linkUrl).hostname}

+

{linkPreview.title}

+
+
+ )} +
+ )} + + {/* Live mode info */} + {mode === "live" && ( + +
+
+ +
+
+

Start a Live Stream

+

Broadcast to your followers in real-time

+
+
+

+ Your location will be visible to viewers. The stream will appear on the map. +

+
+ )} + {/* Title input */} setTitle(e.target.value)} - className="w-full px-4 py-3 mb-3 rounded-xl - bg-surface-800/50 border border-white/10 - text-surface-100 text-lg font-medium + className="w-full px-4 py-3 rounded-xl + bg-surface-700/50 border border-white/10 + text-white text-base font-medium placeholder:text-surface-500 - focus:outline-none focus:border-accent/50 - transition-colors" + focus:outline-none focus:border-cyan-500/50" /> - {/* Body textarea */} -