feat(settings/profile): move share to per-product, profile share on card header
- Each product row now has an ↗ share button (product URL shared, not profile) WhatsApp message: "Found this for our baby — [title]: [url]" - Only one product share sheet open at a time; closes on backdrop tap - Profile page share (↗) moved to the profile card header row where it contextually belongs — shares the /m/slug link, not a product - Removed the share button that was on the Products section header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8598259fb1
commit
fb402e9898
1 changed files with 114 additions and 82 deletions
|
|
@ -34,7 +34,8 @@ export default function ProfileSettingsPage() {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [saveMsg, setSaveMsg] = useState("");
|
const [saveMsg, setSaveMsg] = useState("");
|
||||||
const [profileExpanded, setProfileExpanded] = useState(true);
|
const [profileExpanded, setProfileExpanded] = useState(true);
|
||||||
const [showShareSheet, setShowShareSheet] = useState(false);
|
const [shareProductId, setShareProductId] = useState<string | null>(null);
|
||||||
|
const [profileShareOpen, setProfileShareOpen] = useState(false);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const [slug, setSlug] = useState("");
|
const [slug, setSlug] = useState("");
|
||||||
|
|
@ -89,21 +90,20 @@ export default function ProfileSettingsPage() {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyLink() {
|
async function copyLink(url: string) {
|
||||||
if (!profileUrl) return;
|
await navigator.clipboard.writeText(url);
|
||||||
await navigator.clipboard.writeText(profileUrl);
|
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareViaWhatsApp() {
|
function shareViaWhatsApp(url: string, text: string) {
|
||||||
const text = encodeURIComponent(`Check out my baby product recommendations! ${profileUrl}`);
|
const msg = encodeURIComponent(`${text} ${url}`);
|
||||||
window.open(`https://wa.me/?text=${text}`, "_blank");
|
window.open(`https://wa.me/?text=${msg}`, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shareNative() {
|
async function shareNative(title: string, url: string) {
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
await navigator.share({ title: `${displayName}'s baby picks`, url: profileUrl });
|
await navigator.share({ title, url });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,13 +190,13 @@ export default function ProfileSettingsPage() {
|
||||||
{/* Profile section — collapsible */}
|
{/* Profile section — collapsible */}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden">
|
||||||
{/* Always-visible header row */}
|
{/* Always-visible header row */}
|
||||||
|
<div className="flex items-center justify-between px-4 py-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setProfileExpanded(v => !v)}
|
onClick={() => setProfileExpanded(v => !v)}
|
||||||
className="w-full flex items-center justify-between p-4"
|
className="flex items-center gap-3 flex-1 text-left"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-xl">👤</span>
|
<span className="text-xl">👤</span>
|
||||||
<div className="text-left">
|
<div>
|
||||||
<p className="font-semibold text-gray-800 dark:text-white text-sm">
|
<p className="font-semibold text-gray-800 dark:text-white text-sm">
|
||||||
{displayName || "Set up your profile"}
|
{displayName || "Set up your profile"}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -204,9 +204,50 @@ export default function ProfileSettingsPage() {
|
||||||
<p className="text-xs text-gray-400">{baseUrl}/m/{slug}</p>
|
<p className="text-xs text-gray-400">{baseUrl}/m/{slug}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<span className={`text-gray-400 text-sm transition-transform ${profileExpanded ? "rotate-180" : ""}`}>▼</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* Profile page share — only when slug is set */}
|
||||||
|
{slug && slugValid && (
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={e => { e.stopPropagation(); setProfileShareOpen(v => !v); }}
|
||||||
|
className="p-2 rounded-xl bg-rose-50 dark:bg-rose-900/20 text-rose-500 text-sm"
|
||||||
|
title="Share your profile page"
|
||||||
|
>
|
||||||
|
↗
|
||||||
|
</button>
|
||||||
|
{profileShareOpen && (
|
||||||
|
<>
|
||||||
|
<div className="fixed inset-0 z-30" onClick={() => setProfileShareOpen(false)} />
|
||||||
|
<div className="absolute right-0 top-9 z-40 bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 p-3 w-52 space-y-1">
|
||||||
|
<p className="text-xs text-gray-400 px-2 pb-1">Share your profile page</p>
|
||||||
|
<button onClick={() => { copyLink(profileUrl); setProfileShareOpen(false); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">{copied ? "✅" : "📋"}</span>
|
||||||
|
{copied ? "Copied!" : "Copy link"}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { shareViaWhatsApp(profileUrl, "Check out my baby product recommendations!"); setProfileShareOpen(false); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">💬</span>
|
||||||
|
WhatsApp
|
||||||
|
</button>
|
||||||
|
{typeof navigator !== "undefined" && "share" in navigator && (
|
||||||
|
<button onClick={() => { shareNative(`${displayName}'s baby picks`, profileUrl); setProfileShareOpen(false); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">📤</span>
|
||||||
|
More options
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button onClick={() => setProfileExpanded(v => !v)} className="p-2 text-gray-400 text-sm">
|
||||||
|
<span className={`inline-block transition-transform ${profileExpanded ? "rotate-180" : ""}`}>▼</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Expanded form */}
|
{/* Expanded form */}
|
||||||
{profileExpanded && (
|
{profileExpanded && (
|
||||||
|
|
@ -281,51 +322,6 @@ export default function ProfileSettingsPage() {
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-sm">
|
<div className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-sm">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="font-semibold text-gray-800 dark:text-white">Product Recommendations</h2>
|
<h2 className="font-semibold text-gray-800 dark:text-white">Product Recommendations</h2>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{/* Share button — only visible when profile has a slug */}
|
|
||||||
{slug && slugValid && (
|
|
||||||
<div className="relative">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowShareSheet(v => !v)}
|
|
||||||
className="flex items-center gap-1 px-2.5 py-1.5 rounded-xl bg-rose-50 dark:bg-rose-900/20 text-rose-500 text-sm font-medium"
|
|
||||||
>
|
|
||||||
<span>↗</span>
|
|
||||||
<span>Share</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showShareSheet && (
|
|
||||||
<>
|
|
||||||
<div className="fixed inset-0 z-30" onClick={() => setShowShareSheet(false)} />
|
|
||||||
<div className="absolute right-0 top-9 z-40 bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 p-3 w-52 space-y-1">
|
|
||||||
<p className="text-xs text-gray-400 px-2 pb-1">Share your page</p>
|
|
||||||
<button
|
|
||||||
onClick={() => { copyLink(); setShowShareSheet(false); }}
|
|
||||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
<span className="text-lg">{copied ? "✅" : "📋"}</span>
|
|
||||||
{copied ? "Copied!" : "Copy link"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => { shareViaWhatsApp(); setShowShareSheet(false); }}
|
|
||||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
<span className="text-lg">💬</span>
|
|
||||||
WhatsApp
|
|
||||||
</button>
|
|
||||||
{typeof navigator !== "undefined" && "share" in navigator && (
|
|
||||||
<button
|
|
||||||
onClick={() => { shareNative(); setShowShareSheet(false); }}
|
|
||||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
<span className="text-lg">📤</span>
|
|
||||||
More options
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { resetProductForm(); setShowAddProduct(true); }}
|
onClick={() => { resetProductForm(); setShowAddProduct(true); }}
|
||||||
className="text-sm text-rose-500 font-medium px-2.5 py-1.5"
|
className="text-sm text-rose-500 font-medium px-2.5 py-1.5"
|
||||||
|
|
@ -333,7 +329,6 @@ export default function ProfileSettingsPage() {
|
||||||
+ Add
|
+ Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add/Edit form */}
|
{/* Add/Edit form */}
|
||||||
{showAddProduct && (
|
{showAddProduct && (
|
||||||
|
|
@ -373,7 +368,8 @@ export default function ProfileSettingsPage() {
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{products.map((p, i) => (
|
{products.map((p, i) => (
|
||||||
<div key={p.id} className="flex items-center gap-3 p-3 rounded-xl bg-gray-50 dark:bg-gray-700/50">
|
<div key={p.id} className="rounded-xl bg-gray-50 dark:bg-gray-700/50 overflow-visible">
|
||||||
|
<div className="flex items-center gap-3 p-3">
|
||||||
<div className="text-2xl">{CATEGORY_EMOJI[p.category] || "🛍️"}</div>
|
<div className="text-2xl">{CATEGORY_EMOJI[p.category] || "🛍️"}</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-gray-800 dark:text-white truncate">{p.title}</p>
|
<p className="text-sm font-medium text-gray-800 dark:text-white truncate">{p.title}</p>
|
||||||
|
|
@ -385,9 +381,45 @@ export default function ProfileSettingsPage() {
|
||||||
<button onClick={() => moveProduct(p.id, "down")} disabled={i === products.length - 1}
|
<button onClick={() => moveProduct(p.id, "down")} disabled={i === products.length - 1}
|
||||||
className="text-gray-400 disabled:opacity-30 text-xs px-1.5 py-1">↓</button>
|
className="text-gray-400 disabled:opacity-30 text-xs px-1.5 py-1">↓</button>
|
||||||
<button onClick={() => startEdit(p)} className="text-rose-400 text-xs px-1.5 py-1">Edit</button>
|
<button onClick={() => startEdit(p)} className="text-rose-400 text-xs px-1.5 py-1">Edit</button>
|
||||||
|
{/* Per-product share */}
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setShareProductId(id => id === p.id ? null : p.id)}
|
||||||
|
className="text-rose-400 text-xs px-1.5 py-1"
|
||||||
|
title="Share this product"
|
||||||
|
>
|
||||||
|
↗
|
||||||
|
</button>
|
||||||
|
{shareProductId === p.id && (
|
||||||
|
<>
|
||||||
|
<div className="fixed inset-0 z-30" onClick={() => setShareProductId(null)} />
|
||||||
|
<div className="absolute right-0 bottom-8 z-40 bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 p-3 w-52 space-y-1">
|
||||||
|
<p className="text-xs text-gray-400 px-2 pb-1 truncate">{p.title}</p>
|
||||||
|
<button onClick={() => { copyLink(p.url); setShareProductId(null); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">{copied ? "✅" : "📋"}</span>
|
||||||
|
{copied ? "Copied!" : "Copy link"}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { shareViaWhatsApp(p.url, `Found this for our baby — ${p.title}:`); setShareProductId(null); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">💬</span>
|
||||||
|
WhatsApp
|
||||||
|
</button>
|
||||||
|
{typeof navigator !== "undefined" && "share" in navigator && (
|
||||||
|
<button onClick={() => { shareNative(p.title, p.url); setShareProductId(null); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 text-sm text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="text-lg">📤</span>
|
||||||
|
More options
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<button onClick={() => deleteProduct(p.id)} className="text-gray-400 text-xs px-1.5 py-1">✕</button>
|
<button onClick={() => deleteProduct(p.id)} className="text-gray-400 text-xs px-1.5 py-1">✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue