update frontend

This commit is contained in:
Your Name 2025-12-10 23:36:13 -05:00
parent c4a6c3cddc
commit fbaee6ab17
4 changed files with 162 additions and 94 deletions

View File

@ -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)}
> >
<div className="sw-icon-circle">
<i className={btn.icon} /> <i className={btn.icon} />
<div className="sw-filter-label">{btn.label}</div> </div>
<div className="sw-icon-label">{btn.label}</div>
</button> </button>
))} ))}
</div> </div>

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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;
} }