From ceeb80df03a86703bc6c4e79fe3dc9d3add2e772 Mon Sep 17 00:00:00 2001 From: Patrick Britton Date: Mon, 9 Feb 2026 09:37:44 -0600 Subject: [PATCH] feat: admin profile editing and follower/following management for official accounts --- admin/src/app/users/[id]/page.tsx | 235 +- admin/src/lib/api.ts | 18 + go-backend/cmd/api/main.go | 3 + go-backend/internal/handlers/admin_handler.go | 198 + logo.ai | 3896 +++++++++-------- media/official accounts/newsicon.png | Bin 0 -> 15955 bytes 6 files changed, 2571 insertions(+), 1779 deletions(-) create mode 100644 media/official accounts/newsicon.png diff --git a/admin/src/app/users/[id]/page.tsx b/admin/src/app/users/[id]/page.tsx index be1bcd0..4f1d00d 100644 --- a/admin/src/app/users/[id]/page.tsx +++ b/admin/src/app/users/[id]/page.tsx @@ -5,7 +5,7 @@ import { api } from '@/lib/api'; import { statusColor, formatDateTime } from '@/lib/utils'; import { useEffect, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; -import { ArrowLeft, Shield, Ban, CheckCircle, XCircle, Star, RotateCcw } from 'lucide-react'; +import { ArrowLeft, Shield, Ban, CheckCircle, XCircle, Star, RotateCcw, Pencil, UserPlus, UserMinus, Users, Save, X } from 'lucide-react'; import Link from 'next/link'; export default function UserDetailPage() { @@ -255,6 +255,16 @@ export default function UserDetailPage() { + + {/* Official Account: Editable Profile */} + {user.is_official && ( + + )} + + {/* Official Account: Follower/Following Management */} + {user.is_official && ( + + )} ) : (
User not found
@@ -322,3 +332,226 @@ export default function UserDetailPage() { ); } + +// ─── Official Profile Editor ───────────────────────── +function OfficialProfileEditor({ user, onSaved }: { user: any; onSaved: () => void }) { + const [editing, setEditing] = useState(false); + const [saving, setSaving] = useState(false); + const [result, setResult] = useState<{ ok: boolean; msg: string } | null>(null); + const [form, setForm] = useState({ + handle: user.handle || '', + display_name: user.display_name || '', + bio: user.bio || '', + avatar_url: user.avatar_url || '', + cover_url: user.cover_url || '', + location: user.location || '', + website: user.website || '', + origin_country: user.origin_country || '', + }); + + useEffect(() => { + setForm({ + handle: user.handle || '', + display_name: user.display_name || '', + bio: user.bio || '', + avatar_url: user.avatar_url || '', + cover_url: user.cover_url || '', + location: user.location || '', + website: user.website || '', + origin_country: user.origin_country || '', + }); + }, [user]); + + const handleSave = async () => { + setSaving(true); + setResult(null); + try { + await api.adminUpdateProfile(user.id, form); + setResult({ ok: true, msg: 'Profile saved' }); + setEditing(false); + onSaved(); + } catch (e: any) { + setResult({ ok: false, msg: e.message }); + } + setSaving(false); + }; + + const fields: { key: keyof typeof form; label: string; type?: string }[] = [ + { key: 'handle', label: 'Handle' }, + { key: 'display_name', label: 'Display Name' }, + { key: 'bio', label: 'Bio', type: 'textarea' }, + { key: 'avatar_url', label: 'Avatar URL' }, + { key: 'cover_url', label: 'Cover URL' }, + { key: 'location', label: 'Location' }, + { key: 'website', label: 'Website' }, + { key: 'origin_country', label: 'Country' }, + ]; + + return ( +
+
+

+ Official Account Profile +

+ {!editing ? ( + + ) : ( +
+ + +
+ )} +
+ + {result && ( +

{result.msg}

+ )} + +
+ {fields.map((f) => ( +
+ + {editing ? ( + f.type === 'textarea' ? ( +