Updating auth
This commit is contained in:
183
ui/src/components/MapListModal.tsx
Normal file
183
ui/src/components/MapListModal.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { useState } from "react";
|
||||
import { api } from "../api";
|
||||
import type { ListedMap } from "../types";
|
||||
import "./MapListModal.css";
|
||||
|
||||
interface Props {
|
||||
maps: ListedMap[];
|
||||
selectedMapId: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
onClose: () => void;
|
||||
onMapsChange: (maps: ListedMap[]) => void;
|
||||
}
|
||||
|
||||
/** Copy text to the clipboard; show a brief "Copied!" toast. */
|
||||
function copyToClipboard(
|
||||
text: string,
|
||||
setCopied: (id: string | null) => void,
|
||||
id: string,
|
||||
) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopied(id);
|
||||
setTimeout(() => setCopied(null), 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function accessLabel(access: string): string {
|
||||
switch (access) {
|
||||
case "public_view":
|
||||
return "Public (view)";
|
||||
case "public_edit":
|
||||
return "Public (edit)";
|
||||
default:
|
||||
return "Private";
|
||||
}
|
||||
}
|
||||
|
||||
export default function MapListModal({
|
||||
maps,
|
||||
selectedMapId,
|
||||
onSelect,
|
||||
onClose,
|
||||
onMapsChange,
|
||||
}: Props) {
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
||||
const [togglingId, setTogglingId] = useState<string | null>(null);
|
||||
|
||||
async function handleFavoriteToggle(e: React.MouseEvent, map: ListedMap) {
|
||||
e.stopPropagation();
|
||||
setTogglingId(map.id);
|
||||
try {
|
||||
if (map.is_favorited) {
|
||||
await api.unfavoriteMap(map.id);
|
||||
} else {
|
||||
await api.favoriteMap(map.id);
|
||||
}
|
||||
onMapsChange(
|
||||
maps.map((m) =>
|
||||
m.id === map.id ? { ...m, is_favorited: !m.is_favorited } : m,
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to toggle favorite", err);
|
||||
} finally {
|
||||
setTogglingId(null);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopyLink(e: React.MouseEvent, map: ListedMap) {
|
||||
e.stopPropagation();
|
||||
const link = `${window.location.origin}/map/${encodeURIComponent(map.id)}`;
|
||||
copyToClipboard(link, setCopiedId, map.id);
|
||||
}
|
||||
|
||||
function handleSelect(map: ListedMap) {
|
||||
onSelect(map.id);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div
|
||||
className="modal map-list-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="modal-header">
|
||||
<h2>My Maps</h2>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{maps.length === 0 ? (
|
||||
<p className="map-list-empty">
|
||||
No maps yet. Click "+ New Map" to create one.
|
||||
</p>
|
||||
) : (
|
||||
<div className="map-list-scroll">
|
||||
{maps.map((map) => (
|
||||
<div
|
||||
key={map.id}
|
||||
className={`map-list-row ${map.id === selectedMapId ? "active" : ""}`}
|
||||
onClick={() => handleSelect(map)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSelect(map)}
|
||||
>
|
||||
<div className="map-list-main">
|
||||
<span className="map-list-name">{map.name}</span>
|
||||
<div className="map-list-meta">
|
||||
<span className="map-list-owner">
|
||||
by {map.owner_username}
|
||||
</span>
|
||||
<span
|
||||
className={`map-access-badge access-${map.public_access}`}
|
||||
>
|
||||
{accessLabel(map.public_access)}
|
||||
</span>
|
||||
{map.user_role && (
|
||||
<span className={`perm-role-badge role-${map.user_role}`}>
|
||||
{map.user_role}
|
||||
</span>
|
||||
)}
|
||||
{map.is_favorited && !map.user_role && (
|
||||
<span className="map-fav-badge">★ Favorited</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="map-list-actions">
|
||||
{/* Favorite toggle */}
|
||||
<button
|
||||
className={`map-action-btn fav-btn ${map.is_favorited ? "fav-active" : ""}`}
|
||||
onClick={(e) => handleFavoriteToggle(e, map)}
|
||||
disabled={togglingId === map.id}
|
||||
title={
|
||||
map.is_favorited
|
||||
? "Remove from favorites"
|
||||
: "Add to favorites"
|
||||
}
|
||||
>
|
||||
{map.is_favorited ? "★" : "☆"}
|
||||
</button>
|
||||
|
||||
{/* Copy link */}
|
||||
<button
|
||||
className="map-action-btn copy-btn"
|
||||
onClick={(e) => handleCopyLink(e, map)}
|
||||
title="Copy link"
|
||||
>
|
||||
{copiedId === map.id ? (
|
||||
<span className="copied-text">✓</span>
|
||||
) : (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect
|
||||
x="9"
|
||||
y="9"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2"
|
||||
ry="2"
|
||||
/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user