update frontend
This commit is contained in:
parent
c4a6c3cddc
commit
fbaee6ab17
|
|
@ -1,11 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import "../../styles/filters.css";
|
import "../../styles/filters.css";
|
||||||
|
|
||||||
/**
|
|
||||||
* Vertical glossy filter buttons for the main categories.
|
|
||||||
* active: string in { "All", "News", "Friends", "Events", "Market" }
|
|
||||||
* onSelect(code): called with the category code.
|
|
||||||
*/
|
|
||||||
export default function FilterButtons({ active, onSelect }) {
|
export default function FilterButtons({ active, onSelect }) {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{ code: "All", label: "All", icon: "fa-solid fa-globe" },
|
{ code: "All", label: "All", icon: "fa-solid fa-globe" },
|
||||||
|
|
@ -16,19 +11,21 @@ export default function FilterButtons({ active, onSelect }) {
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sw-filter-column">
|
<div className="sw-icon-column">
|
||||||
{buttons.map((btn) => (
|
{buttons.map((btn) => (
|
||||||
<button
|
<button
|
||||||
key={btn.code}
|
key={btn.code}
|
||||||
type="button"
|
type="button"
|
||||||
className={
|
className={
|
||||||
"sw-filter-btn" +
|
"sw-icon-btn" +
|
||||||
(active === btn.code ? " sw-filter-active" : "")
|
(active === btn.code ? " sw-icon-active" : "")
|
||||||
}
|
}
|
||||||
onClick={() => onSelect && onSelect(btn.code)}
|
onClick={() => onSelect && onSelect(btn.code)}
|
||||||
>
|
>
|
||||||
<i className={btn.icon} />
|
<div className="sw-icon-circle">
|
||||||
<div className="sw-filter-label">{btn.label}</div>
|
<i className={btn.icon} />
|
||||||
|
</div>
|
||||||
|
<div className="sw-icon-label">{btn.label}</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from "react";
|
||||||
|
import "../../styles/filters.css";
|
||||||
|
|
||||||
|
export default function TimeFilterButtons({ active, onSelect }) {
|
||||||
|
const buttons = [
|
||||||
|
{ code: "NOW", label: "Now", icon: "fa-solid fa-bolt" },
|
||||||
|
{ code: "TODAY", label: "Today", icon: "fa-solid fa-sun" },
|
||||||
|
{ code: "RECENT", label: "Recent", icon: "fa-solid fa-clock-rotate-left" },
|
||||||
|
{ code: "PAST", label: "Past", icon: "fa-solid fa-hourglass-half" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sw-icon-column">
|
||||||
|
{buttons.map((btn) => (
|
||||||
|
<button
|
||||||
|
key={btn.code}
|
||||||
|
type="button"
|
||||||
|
className={
|
||||||
|
"sw-icon-btn" +
|
||||||
|
(active === btn.code ? " sw-icon-active" : "")
|
||||||
|
}
|
||||||
|
onClick={() => onSelect && onSelect(btn.code)}
|
||||||
|
>
|
||||||
|
<div className="sw-icon-circle">
|
||||||
|
<i className={btn.icon} />
|
||||||
|
</div>
|
||||||
|
<div className="sw-icon-label">{btn.label}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import { usePostsEngine } from "./usePostsEngine";
|
||||||
import PostList from "../Posts/PostList";
|
import PostList from "../Posts/PostList";
|
||||||
import { useAuth } from "../../auth/AuthContext";
|
import { useAuth } from "../../auth/AuthContext";
|
||||||
import FilterButtons from "../Filters/FilterButtons";
|
import FilterButtons from "../Filters/FilterButtons";
|
||||||
|
import TimeFilterButtons from "../Filters/TimeFilterButtons";
|
||||||
|
|
||||||
export default function MapView({ theme = "dark" }) {
|
export default function MapView({ theme = "dark" }) {
|
||||||
const { authenticated, username, token } = useAuth();
|
const { authenticated, username, token } = useAuth();
|
||||||
|
|
@ -95,7 +96,7 @@ export default function MapView({ theme = "dark" }) {
|
||||||
handleIncomingPost(msg.post);
|
handleIncomingPost(msg.post);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore bad messages
|
// ignore
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return () => ws && ws.close();
|
return () => ws && ws.close();
|
||||||
|
|
@ -139,7 +140,7 @@ export default function MapView({ theme = "dark" }) {
|
||||||
|
|
||||||
const authorName = (username || "").trim() || "anon";
|
const authorName = (username || "").trim() || "anon";
|
||||||
|
|
||||||
// Crosshair offset: adjust so big bubble sits correctly above pointer
|
// Crosshair offset so the big bubble appears above the pointer
|
||||||
let baseLng;
|
let baseLng;
|
||||||
let baseLat;
|
let baseLat;
|
||||||
|
|
||||||
|
|
@ -152,7 +153,7 @@ export default function MapView({ theme = "dark" }) {
|
||||||
baseLat = center.lat;
|
baseLat = center.lat;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixelOffsetY = -20; // tweak if needed
|
const pixelOffsetY = -20;
|
||||||
|
|
||||||
const screenPoint = map.project([baseLng, baseLat]);
|
const screenPoint = map.project([baseLng, baseLat]);
|
||||||
const correctedPoint = {
|
const correctedPoint = {
|
||||||
|
|
@ -184,7 +185,6 @@ export default function MapView({ theme = "dark" }) {
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
// Inject directly into the posts engine in addition to WebSocket broadcast
|
|
||||||
if (newPost && newPost.id) {
|
if (newPost && newPost.id) {
|
||||||
handleIncomingPost(newPost);
|
handleIncomingPost(newPost);
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +209,6 @@ export default function MapView({ theme = "dark" }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map right-side category selection from FilterButtons
|
|
||||||
const handleMainFilterSelect = (code) => {
|
const handleMainFilterSelect = (code) => {
|
||||||
if (!FILTER_MAIN_CATEGORIES.includes(code)) return;
|
if (!FILTER_MAIN_CATEGORIES.includes(code)) return;
|
||||||
setMainFilter(code);
|
setMainFilter(code);
|
||||||
|
|
@ -230,45 +229,39 @@ export default function MapView({ theme = "dark" }) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* LEFT (time filters) */}
|
{/* LEFT: time filters as icon+label */}
|
||||||
<div className="map-overlay map-overlay-left">
|
<div className="map-overlay map-overlay-left">
|
||||||
{[
|
<TimeFilterButtons
|
||||||
["NOW", "Now"],
|
active={timeFilter}
|
||||||
["TODAY", "Today"],
|
onSelect={(code) => setTimeFilter(code)}
|
||||||
["RECENT", "Recently"],
|
/>
|
||||||
["PAST", "Past"],
|
|
||||||
].map(([code, label]) => (
|
|
||||||
<button
|
|
||||||
key={code}
|
|
||||||
className={
|
|
||||||
timeFilter === code ? "chip-round chip-selected" : "chip-round"
|
|
||||||
}
|
|
||||||
onClick={() => setTimeFilter(code)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT (main categories with glossy icons) */}
|
{/* RIGHT: main categories as icon+label */}
|
||||||
<div className="map-overlay map-overlay-right">
|
<div className="map-overlay map-overlay-right">
|
||||||
<FilterButtons active={mainFilter} onSelect={handleMainFilterSelect} />
|
<FilterButtons
|
||||||
|
active={mainFilter}
|
||||||
|
onSelect={handleMainFilterSelect}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BOTTOM (subcategories glossy pills) */}
|
{/* BOTTOM: subcategories icons */}
|
||||||
<div className="map-overlay map-overlay-bottom">
|
<div className="map-overlay map-overlay-bottom">
|
||||||
<div className="sw-bottom-bar">
|
<div className="sw-bottom-row">
|
||||||
{bottomCategories.map((c) => (
|
{bottomCategories.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c}
|
key={c}
|
||||||
type="button"
|
type="button"
|
||||||
className={
|
className={
|
||||||
"sw-bottom-btn" +
|
"sw-bottom-item" +
|
||||||
(subFilter === c ? " sw-bottom-btn-active" : "")
|
(subFilter === c ? " sw-bottom-active" : "")
|
||||||
}
|
}
|
||||||
onClick={() => setSubFilter(c)}
|
onClick={() => setSubFilter(c)}
|
||||||
>
|
>
|
||||||
{c}
|
<div className="sw-bottom-circle">
|
||||||
|
{c === "All" ? "All" : c.slice(0, 2)}
|
||||||
|
</div>
|
||||||
|
<div className="sw-bottom-label">{c}</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,80 +1,126 @@
|
||||||
/* ===== SocioWire glossy filter buttons ===== */
|
/* ===== Generic vertical icon column (right & left) ===== */
|
||||||
|
|
||||||
/* Vertical circular category buttons (right side) */
|
.sw-icon-column {
|
||||||
.sw-filter-column {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.6rem;
|
gap: 0.55rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-filter-btn {
|
.sw-icon-btn {
|
||||||
width: 70px;
|
background: none;
|
||||||
height: 70px;
|
border: none;
|
||||||
border-radius: 50%;
|
padding: 0;
|
||||||
background: radial-gradient(circle at 30% 30%, #38bdf8, #1d4ed8, #020617);
|
cursor: pointer;
|
||||||
border: 2px solid rgba(255,255,255,0.2);
|
|
||||||
box-shadow: 0 4px 14px rgba(0,0,0,0.7);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-icon-circle {
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(15, 23, 42, 0.86);
|
||||||
|
border: 1.5px solid rgba(148, 163, 184, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #e5f0ff;
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.75);
|
||||||
cursor: pointer;
|
transition:
|
||||||
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
|
transform 0.12s ease,
|
||||||
|
box-shadow 0.12s ease,
|
||||||
|
border-color 0.12s ease,
|
||||||
|
background 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-filter-btn i {
|
.sw-icon-circle i {
|
||||||
font-size: 24px;
|
font-size: 22px;
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-filter-label {
|
.sw-icon-label {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-shadow: 0 1px 2px rgba(0,0,0,0.9);
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-filter-btn:hover {
|
.sw-icon-btn:hover .sw-icon-circle {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 7px 20px rgba(0,0,0,0.8);
|
box-shadow: 0 5px 14px rgba(0, 0, 0, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-filter-active {
|
.sw-icon-btn:active .sw-icon-circle {
|
||||||
border-color: #38bdf8;
|
transform: scale(0.96);
|
||||||
box-shadow: 0 0 18px rgba(56,189,248,0.9);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Bottom sub-category glossy pills ===== */
|
/* active state: same color for icon + background, more glow */
|
||||||
|
.sw-icon-active .sw-icon-circle {
|
||||||
.sw-bottom-bar {
|
background: rgba(37, 99, 235, 0.82);
|
||||||
display: flex;
|
border-color: #60a5fa;
|
||||||
gap: 0.4rem;
|
box-shadow: 0 0 14px rgba(56, 189, 248, 0.95);
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
background: radial-gradient(circle at 0% 0%, rgba(15,23,42,0.96), rgba(15,23,42,0.85));
|
|
||||||
border-radius: 999px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.75);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-bottom-btn {
|
.sw-icon-active .sw-icon-label {
|
||||||
border-radius: 999px;
|
color: #bfdbfe;
|
||||||
padding: 0.25rem 0.75rem;
|
font-weight: 600;
|
||||||
font-size: 0.75rem;
|
}
|
||||||
border: 1px solid rgba(148,163,184,0.6);
|
|
||||||
background: rgba(15,23,42,0.9);
|
/* ===== Bottom sub-category icons (row) ===== */
|
||||||
color: #e5e7eb;
|
|
||||||
cursor: pointer;
|
.sw-bottom-row {
|
||||||
white-space: nowrap;
|
display: flex;
|
||||||
transition: background .12s ease, transform .12s ease, box-shadow .12s ease;
|
gap: 0.35rem;
|
||||||
}
|
padding: 0.2rem 0.6rem;
|
||||||
|
background: rgba(15, 23, 42, 0.94);
|
||||||
.sw-bottom-btn:hover {
|
border-radius: 999px;
|
||||||
transform: translateY(-2px);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.85);
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.7);
|
}
|
||||||
}
|
|
||||||
|
.sw-bottom-item {
|
||||||
.sw-bottom-btn-active {
|
border: none;
|
||||||
background: linear-gradient(135deg, #38bdf8, #1d4ed8);
|
background: none;
|
||||||
border-color: #93c5fd;
|
padding: 0;
|
||||||
color: #0b1120;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.12rem;
|
||||||
|
min-width: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-bottom-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(15, 23, 42, 0.86);
|
||||||
|
border: 1.5px solid rgba(148, 163, 184, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-bottom-label {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: #e5e7eb;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-bottom-item:hover .sw-bottom-circle {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 5px 14px rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-bottom-active .sw-bottom-circle {
|
||||||
|
background: rgba(37, 99, 235, 0.82);
|
||||||
|
border-color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-bottom-active .sw-bottom-label {
|
||||||
|
color: #bfdbfe;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue