v115: Use existing FAB for island posts with island_username context
- FAB (+) button now context-aware - includes island_username when on island - PostComposer accepts islandUsername prop and includes in payload - Removed custom post buttons from island terrain view - Global __activeIslandContext set when viewing an island - App.jsx handles sw:openPostCreator event for island posts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
717e5f64a1
commit
fa90d7426b
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "sociowire-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.114",
|
||||
"version": "0.0.115",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
|
|
|
|||
46
src/App.jsx
46
src/App.jsx
|
|
@ -7,7 +7,7 @@ import ContentFilters from "./components/Filters/ContentFilters";
|
|||
import IslandViewer from "./components/Island/IslandViewer";
|
||||
import ChatContainer from "./components/Chat/ChatContainer";
|
||||
import PostDetailModal from "./components/Posts/PostDetailModal";
|
||||
import { CreatePostFAB } from "./components/Posts/PostComposer";
|
||||
import PostComposer, { CreatePostFAB } from "./components/Posts/PostComposer";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { fetchIslandByUsername } from "./api/client";
|
||||
|
||||
|
|
@ -66,6 +66,10 @@ export default function App() {
|
|||
// Island Viewer state (for viewing individual islands)
|
||||
const [selectedIsland, setSelectedIsland] = useState(null);
|
||||
|
||||
// Post Composer state (for creating posts with island context)
|
||||
const [showPostComposer, setShowPostComposer] = useState(false);
|
||||
const [postComposerIsland, setPostComposerIsland] = useState(null);
|
||||
|
||||
// Post detail modal state (triggered from map markers)
|
||||
const [detailPost, setDetailPost] = useState(null);
|
||||
|
||||
|
|
@ -152,16 +156,30 @@ export default function App() {
|
|||
return () => window.removeEventListener("sw:openPostDetail", handleOpenPostDetail);
|
||||
}, []);
|
||||
|
||||
// Listen for island post creation event
|
||||
// Listen for post creator events
|
||||
useEffect(() => {
|
||||
// Handle opening post composer with island context
|
||||
const handleOpenPostCreator = (e) => {
|
||||
const { island_username } = e.detail || {};
|
||||
setPostComposerIsland(island_username || null);
|
||||
setShowPostComposer(true);
|
||||
};
|
||||
|
||||
// Legacy island post event
|
||||
const handleIslandPost = (e) => {
|
||||
const { island } = e.detail || {};
|
||||
if (island) {
|
||||
window.location.href = `/island/${island}?create=true`;
|
||||
setPostComposerIsland(island);
|
||||
setShowPostComposer(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('sw:openPostCreator', handleOpenPostCreator);
|
||||
window.addEventListener('sociowire:create-post', handleIslandPost);
|
||||
return () => window.removeEventListener('sociowire:create-post', handleIslandPost);
|
||||
return () => {
|
||||
window.removeEventListener('sw:openPostCreator', handleOpenPostCreator);
|
||||
window.removeEventListener('sociowire:create-post', handleIslandPost);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// World navigation handlers
|
||||
|
|
@ -362,6 +380,26 @@ export default function App() {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Post Composer Modal (for island posts) */}
|
||||
<AnimatePresence>
|
||||
{showPostComposer && (
|
||||
<PostComposer
|
||||
key="post-composer"
|
||||
islandUsername={postComposerIsland}
|
||||
onClose={() => {
|
||||
setShowPostComposer(false);
|
||||
setPostComposerIsland(null);
|
||||
}}
|
||||
onPostCreated={(newPost) => {
|
||||
// Refresh wall posts if on map view
|
||||
if (currentView === 'map') {
|
||||
setWallPosts((prev) => [newPost, ...prev]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Floating Action Button for creating posts */}
|
||||
<CreatePostFAB />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const CATEGORIES = [
|
|||
{ id: "weather", icon: "fa-cloud-sun", label: "Weather", color: "from-sky-500 to-blue-500" },
|
||||
];
|
||||
|
||||
export default function PostComposer({ onClose, onPostCreated }) {
|
||||
export default function PostComposer({ onClose, onPostCreated, islandUsername }) {
|
||||
const { t } = useTranslation();
|
||||
const { token, username, isAuthenticated } = useAuth();
|
||||
const fileInputRef = useRef(null);
|
||||
|
|
@ -159,6 +159,11 @@ export default function PostComposer({ onClose, onPostCreated }) {
|
|||
payload.location_name = locationName || "";
|
||||
}
|
||||
|
||||
// Add island context if posting to an island
|
||||
if (islandUsername) {
|
||||
payload.island_username = islandUsername;
|
||||
}
|
||||
|
||||
// Add media URLs
|
||||
if (uploadedMedia.length > 0) {
|
||||
const firstMedia = uploadedMedia[0];
|
||||
|
|
@ -493,10 +498,20 @@ export default function PostComposer({ onClose, onPostCreated }) {
|
|||
}
|
||||
|
||||
// Floating Action Button for creating posts - triggers the map's create post flow
|
||||
// Context-aware: if viewing an island, includes island info
|
||||
export function CreatePostFAB() {
|
||||
const handleClick = () => {
|
||||
// Dispatch event to open the map's post creation modal
|
||||
// Check if we're viewing an island
|
||||
const islandContext = window.__activeIslandContext;
|
||||
if (islandContext && islandContext.isOwner) {
|
||||
// Open post creator with island context
|
||||
window.dispatchEvent(new CustomEvent("sw:openPostCreator", {
|
||||
detail: { island_username: islandContext.username }
|
||||
}));
|
||||
} else {
|
||||
// Default: open map's post creation modal
|
||||
window.dispatchEvent(new CustomEvent("sw:openMapPostCreator"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -30,6 +30,21 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl
|
|||
const [islandPosts, setIslandPosts] = useState([]);
|
||||
const [loadingPosts, setLoadingPosts] = useState(false);
|
||||
|
||||
// Set global island context when zoomed (for FAB to use)
|
||||
useEffect(() => {
|
||||
if (zoomedIsland) {
|
||||
window.__activeIslandContext = {
|
||||
username: zoomedIsland.username,
|
||||
isOwner: username && username.toLowerCase() === (zoomedIsland.username || '').toLowerCase()
|
||||
};
|
||||
} else {
|
||||
window.__activeIslandContext = null;
|
||||
}
|
||||
return () => {
|
||||
window.__activeIslandContext = null;
|
||||
};
|
||||
}, [zoomedIsland, username]);
|
||||
|
||||
// Fetch islands
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
|
@ -289,19 +304,9 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl
|
|||
<i className="fa-solid fa-user" />
|
||||
</button>
|
||||
{username && username.toLowerCase() === (zoomedIsland.username || '').toLowerCase() && (
|
||||
<>
|
||||
<button className="island-terrain-btn" onClick={() => window.location.href = `/island/${zoomedIsland.username}/edit`}>
|
||||
<i className="fa-solid fa-cog" />
|
||||
</button>
|
||||
<button className="island-terrain-btn island-terrain-post-btn" onClick={() => {
|
||||
window.dispatchEvent(new CustomEvent('sociowire:create-post', {
|
||||
detail: { island: zoomedIsland.username }
|
||||
}));
|
||||
}}>
|
||||
<i className="fa-solid fa-plus" />
|
||||
<span>{t('worlds.postHere')}</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -318,14 +323,7 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl
|
|||
<i className="fa-solid fa-umbrella-beach" />
|
||||
<p>{t('worlds.emptyIsland')}</p>
|
||||
{username && username.toLowerCase() === (zoomedIsland.username || '').toLowerCase() && (
|
||||
<button className="island-terrain-first-post" onClick={() => {
|
||||
window.dispatchEvent(new CustomEvent('sociowire:create-post', {
|
||||
detail: { island: zoomedIsland.username }
|
||||
}));
|
||||
}}>
|
||||
<i className="fa-solid fa-plus" />
|
||||
{t('worlds.createFirstPost')}
|
||||
</button>
|
||||
<p className="island-terrain-hint">{t('worlds.useButtonToPost')}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -334,20 +332,6 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* Floating create post button for island owner */}
|
||||
{username && username.toLowerCase() === (zoomedIsland.username || '').toLowerCase() && (
|
||||
<button
|
||||
className="island-create-post-fab"
|
||||
onClick={() => {
|
||||
window.dispatchEvent(new CustomEvent('sociowire:create-post', {
|
||||
detail: { island: zoomedIsland.username }
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-plus" />
|
||||
<span>{t('worlds.newPost')}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue