From 9599adaf0dac3b23b61b590b83552842177a28fd Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Wed, 2 Sep 2026 20:06:32 +0700 Subject: [PATCH] Redesign profile edit form UI/UX + per-line sponsors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restructure EditProfileForm into clean, titled sections (Identity, Bitcoin & Payments, Skate Profile, Account) with gradient icon headers, consistent spacing, and a reusable FieldRow with inline icon + validation status. - Sponsors is now a dynamic per-line list (add/remove rows, Enter to add) instead of a textarea; stored newline-separated. The profile renders each sponsor as its own badge. - Lightning address placeholder is now generic (you@yourdomain.com) — no specific wallet names anywhere. - Nicer image upload: click-the-thumbnail-to-upload with hover overlay, round avatar preview / wide banner preview. - Sticky Save bar at the bottom of the form. Co-authored-by: shakespeare.diy --- src/components/EditProfileForm.tsx | 715 ++++++++++++++++------------- src/pages/NIP19Page.tsx | 24 +- 2 files changed, 407 insertions(+), 332 deletions(-) diff --git a/src/components/EditProfileForm.tsx b/src/components/EditProfileForm.tsx index 804f64a..c02f654 100644 --- a/src/components/EditProfileForm.tsx +++ b/src/components/EditProfileForm.tsx @@ -18,7 +18,7 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; -import { Loader2, Upload, Zap, ShieldCheck, AlertCircle, Bitcoin, Award, MessageSquareLock } from 'lucide-react'; +import { Loader2, Upload, Zap, ShieldCheck, AlertCircle, Bitcoin, Award, MessageSquareLock, UserRound, X, Plus, Bot } from 'lucide-react'; import { NSchema as n, type NostrMetadata } from '@nostrify/nostrify'; import { useQueryClient } from '@tanstack/react-query'; import { useUploadFile } from '@/hooks/useUploadFile'; @@ -52,8 +52,10 @@ export const EditProfileForm: React.FC = () => { const spInvalid = spTrimmed.length > 0 && !isValidSilentPayment(spTrimmed); const spValid = isValidSilentPayment(spTrimmed); - // Sponsorships — freeform highlight (custom kind-0 field `sponsors`). - const [sponsors, setSponsors] = useState(''); + // Sponsorships — one entry per line. Stored as newline-separated text in the + // custom kind-0 field `sponsors`; edited here as a dynamic list of rows. + const [sponsorList, setSponsorList] = useState(['']); + const cleanSponsors = sponsorList.map((s) => s.trim()).filter(Boolean); // Cordn contact link — private (MLS-encrypted) group messaging you can own. // A URL others can use to reach out to you on Cordn (cordn.net). @@ -93,7 +95,10 @@ export const EditProfileForm: React.FC = () => { if (typeof raw.noffer === 'string') setNoffer(raw.noffer); if (typeof metadata.lud16 === 'string') setLud16(metadata.lud16); if (typeof raw.bitcoin_silent_payment === 'string') setSilentPayment(raw.bitcoin_silent_payment); - if (typeof raw.sponsors === 'string') setSponsors(raw.sponsors); + if (typeof raw.sponsors === 'string') { + const lines = raw.sponsors.split('\n').map((s) => s.trim()).filter(Boolean); + setSponsorList(lines.length ? lines : ['']); + } if (typeof raw.cordn === 'string') setCordn(raw.cordn); } }, [metadata, form]); @@ -196,9 +201,9 @@ export const EditProfileForm: React.FC = () => { delete data.bitcoin_silent_payment; } - // Sponsorships highlight (custom field). - if (sponsors.trim()) { - data.sponsors = sponsors.trim(); + // Sponsorships highlight (custom field) — one entry per line. + if (cleanSponsors.length) { + data.sponsors = cleanSponsors.join('\n'); } else { delete data.sponsors; } @@ -258,299 +263,357 @@ export const EditProfileForm: React.FC = () => { } }; + const saving = isPending || isUploading; + return (
- - ( - - Name - - - - - This is your display name that will be displayed to others. - - - - )} - /> + - ( - - Bio - -