feat: upgrade team admin with click-to-edit, Instagram validation, date picker, city autocomplete
- Team list: click card to open editor (remove pencil button), keep drag-to-reorder - Instagram field: username-only input with @ prefix, async account validation via HEAD request - Victory dates: date range picker replacing text input, auto-formats to DD.MM.YYYY / DD-DD.MM.YYYY - Victory location: city autocomplete via Nominatim API with suggestions dropdown - Links: real-time URL validation with error indicators on all link fields - Save button blocked when any validation errors exist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
Plus,
|
||||
Trash2,
|
||||
GripVertical,
|
||||
Pencil,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import type { TeamMember } from "@/types/content";
|
||||
@@ -80,16 +79,23 @@ export default function TeamEditorPage() {
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
const pendingIndex = index;
|
||||
let moved = false;
|
||||
|
||||
function onMove(ev: MouseEvent) {
|
||||
const dx = ev.clientX - x;
|
||||
const dy = ev.clientY - y;
|
||||
if (Math.abs(dx) > 8 || Math.abs(dy) > 8) {
|
||||
moved = true;
|
||||
cleanup();
|
||||
startDrag(ev.clientX, ev.clientY, pendingIndex);
|
||||
}
|
||||
}
|
||||
function onUp() { cleanup(); }
|
||||
function onUp() {
|
||||
cleanup();
|
||||
if (!moved) {
|
||||
window.location.href = `/admin/team/${members[pendingIndex].id}`;
|
||||
}
|
||||
}
|
||||
function cleanup() {
|
||||
window.removeEventListener("mousemove", onMove);
|
||||
window.removeEventListener("mouseup", onUp);
|
||||
@@ -97,7 +103,7 @@ export default function TeamEditorPage() {
|
||||
window.addEventListener("mousemove", onMove);
|
||||
window.addEventListener("mouseup", onUp);
|
||||
},
|
||||
[startDrag]
|
||||
[startDrag, members]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -177,7 +183,7 @@ export default function TeamEditorPage() {
|
||||
key={member.id}
|
||||
ref={(el) => { itemRefs.current[i] = el; }}
|
||||
onMouseDown={(e) => handleCardMouseDown(e, i)}
|
||||
className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors"
|
||||
className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none"
|
||||
@@ -192,14 +198,9 @@ export default function TeamEditorPage() {
|
||||
<p className="font-medium text-white truncate">{member.name}</p>
|
||||
<p className="text-sm text-neutral-400 truncate">{member.role}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/team/${member.id}`} className="rounded p-2 text-neutral-400 hover:text-white transition-colors">
|
||||
<Pencil size={16} />
|
||||
</Link>
|
||||
<button onClick={() => deleteMember(member.id)} className="rounded p-2 text-neutral-400 hover:text-red-400 transition-colors">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={(e) => { e.stopPropagation(); deleteMember(member.id); }} className="rounded p-2 text-neutral-400 hover:text-red-400 transition-colors">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
@@ -237,7 +238,7 @@ export default function TeamEditorPage() {
|
||||
key={member.id}
|
||||
ref={(el) => { itemRefs.current[i] = el; }}
|
||||
onMouseDown={(e) => handleCardMouseDown(e, i)}
|
||||
className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors"
|
||||
className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none"
|
||||
@@ -252,14 +253,9 @@ export default function TeamEditorPage() {
|
||||
<p className="font-medium text-white truncate">{member.name}</p>
|
||||
<p className="text-sm text-neutral-400 truncate">{member.role}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/team/${member.id}`} className="rounded p-2 text-neutral-400 hover:text-white transition-colors">
|
||||
<Pencil size={16} />
|
||||
</Link>
|
||||
<button onClick={() => deleteMember(member.id)} className="rounded p-2 text-neutral-400 hover:text-red-400 transition-colors">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={(e) => { e.stopPropagation(); deleteMember(member.id); }} className="rounded p-2 text-neutral-400 hover:text-red-400 transition-colors">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
visualIndex++;
|
||||
|
||||
Reference in New Issue
Block a user