fix(settings/profile): align My Profile page with app theme

- Background: flat gray → rose-to-amber gradient (matches all other pages)
- Header: add back button with rounded-xl pill style + xl font title
- Buttons: bg-pink-500 → bg-rose-400 throughout
- Loading: spinner → branded emoji bounce (consistent with home screen)
- Toggle: checkbox → styled toggle switch matching app UI language
- Layout: remove desktop max-w-lg wrapper; full-width mobile layout
- Input styling: unified inputClass with focus ring + consistent padding
- Empty state: plain text → emoji + two-line message
- Product rows: border div → bg-gray-50 pill card (matches wardrobe style)
- Add pb-24 so content clears the bottom nav bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-23 19:26:38 +05:30
parent 3d0e6ed46c
commit 3b62841bd4

View file

@ -1,5 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
interface Profile { interface Profile {
id: string; id: string;
@ -25,19 +27,18 @@ const CATEGORIES = ["general", "feeding", "sleep", "play", "clothing"] as const;
const CATEGORY_EMOJI: Record<string, string> = { feeding: "🍼", sleep: "💤", play: "🎮", clothing: "👗", general: "🛍️" }; const CATEGORY_EMOJI: Record<string, string> = { feeding: "🍼", sleep: "💤", play: "🎮", clothing: "👗", general: "🛍️" };
export default function ProfileSettingsPage() { export default function ProfileSettingsPage() {
const router = useRouter();
const [profile, setProfile] = useState<Profile | null>(null); const [profile, setProfile] = useState<Profile | null>(null);
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [saveMsg, setSaveMsg] = useState(""); const [saveMsg, setSaveMsg] = useState("");
// form state
const [slug, setSlug] = useState(""); const [slug, setSlug] = useState("");
const [displayName, setDisplayName] = useState(""); const [displayName, setDisplayName] = useState("");
const [bio, setBio] = useState(""); const [bio, setBio] = useState("");
const [isPublic, setIsPublic] = useState(false); const [isPublic, setIsPublic] = useState(false);
// product form
const [showAddProduct, setShowAddProduct] = useState(false); const [showAddProduct, setShowAddProduct] = useState(false);
const [editingProduct, setEditingProduct] = useState<Product | null>(null); const [editingProduct, setEditingProduct] = useState<Product | null>(null);
const [pTitle, setPTitle] = useState(""); const [pTitle, setPTitle] = useState("");
@ -139,36 +140,47 @@ export default function ProfileSettingsPage() {
const slugValid = /^[a-z0-9-]{3,40}$/.test(slug); const slugValid = /^[a-z0-9-]{3,40}$/.test(slug);
const baseUrl = typeof window !== "undefined" ? window.location.origin : ""; const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
const inputClass = "w-full border border-gray-200 dark:border-gray-600 rounded-xl px-3 py-2.5 text-sm bg-white dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-rose-300";
if (loading) return ( if (loading) return (
<div className="flex items-center justify-center min-h-screen"> <div className="min-h-screen flex flex-col items-center justify-center gap-4 bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-pink-400" /> <div className="flex gap-3 text-4xl">
{["🍼", "😴", "🚼", "👶"].map((e, i) => (
<span key={i} className="animate-bounce" style={{ animationDelay: `${i * 120}ms` }}>{e}</span>
))}
</div>
<p className="text-sm text-gray-400">Loading profile</p>
</div> </div>
); );
return ( return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 pb-20"> <div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800 pb-24">
<div className="max-w-lg mx-auto px-4 py-6"> {/* Header */}
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">My Profile Page</h1> <div className="flex items-center gap-3 p-4">
<button onClick={() => router.back()} className="p-2 rounded-xl bg-white dark:bg-gray-800 shadow-sm text-xl"></button>
<h1 className="text-xl font-bold">My Profile Page</h1>
</div>
<div className="px-4 space-y-4">
{/* Profile section */} {/* Profile section */}
<div className="bg-white dark:bg-gray-800 rounded-2xl p-5 mb-4 shadow-sm"> <div className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-sm">
<h2 className="font-semibold text-gray-800 dark:text-white mb-4">Profile</h2> <h2 className="font-semibold text-gray-800 dark:text-white mb-4">Profile</h2>
<label className="block text-sm text-gray-600 dark:text-gray-400 mb-1">Display Name</label> <label className="block text-sm text-gray-500 dark:text-gray-400 mb-1">Display Name</label>
<input <input
value={displayName} value={displayName}
onChange={e => setDisplayName(e.target.value)} onChange={e => setDisplayName(e.target.value)}
className="w-full border dark:border-gray-600 rounded-xl px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-3" className={`${inputClass} mb-3`}
placeholder="Priya Sharma" placeholder="Priya Sharma"
/> />
<label className="block text-sm text-gray-600 dark:text-gray-400 mb-1">Your Page URL</label> <label className="block text-sm text-gray-500 dark:text-gray-400 mb-1">Your Page URL</label>
<div className="flex items-center gap-1 border dark:border-gray-600 rounded-xl px-3 py-2 text-sm mb-1 dark:bg-gray-700"> <div className="flex items-center gap-1 border border-gray-200 dark:border-gray-600 rounded-xl px-3 py-2.5 text-sm mb-1 bg-white dark:bg-gray-700 focus-within:ring-2 focus-within:ring-rose-300">
<span className="text-gray-400 text-xs">{baseUrl}/m/</span> <span className="text-gray-400 text-xs whitespace-nowrap">{baseUrl}/m/</span>
<input <input
value={slug} value={slug}
onChange={e => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))} onChange={e => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))}
className="flex-1 bg-transparent outline-none dark:text-white" className="flex-1 bg-transparent outline-none dark:text-white text-sm"
placeholder="priya-sharma" placeholder="priya-sharma"
/> />
</div> </div>
@ -176,47 +188,49 @@ export default function ProfileSettingsPage() {
<p className="text-xs text-red-500 mb-2">340 chars, lowercase letters, numbers, hyphens only</p> <p className="text-xs text-red-500 mb-2">340 chars, lowercase letters, numbers, hyphens only</p>
)} )}
<label className="block text-sm text-gray-600 dark:text-gray-400 mb-1 mt-3">Bio <span className="text-gray-400">({bio.length}/200)</span></label> <label className="block text-sm text-gray-500 dark:text-gray-400 mb-1 mt-3">
Bio <span className="text-gray-400">({bio.length}/200)</span>
</label>
<textarea <textarea
value={bio} value={bio}
onChange={e => setBio(e.target.value.slice(0, 200))} onChange={e => setBio(e.target.value.slice(0, 200))}
rows={3} rows={3}
className="w-full border dark:border-gray-600 rounded-xl px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-3 resize-none" className={`${inputClass} mb-3 resize-none`}
placeholder="New mama sharing what works for us 💕" placeholder="New mama sharing what works for us 💕"
/> />
<label className="flex items-center gap-3 cursor-pointer mb-4"> <label className="flex items-center gap-3 cursor-pointer mb-4">
<input <div
type="checkbox" onClick={() => setIsPublic(v => !v)}
checked={isPublic} className={`w-10 h-6 rounded-full transition-colors relative cursor-pointer ${isPublic ? "bg-rose-400" : "bg-gray-300 dark:bg-gray-600"}`}
onChange={e => setIsPublic(e.target.checked)} >
className="w-4 h-4 accent-pink-500" <div className={`absolute top-1 w-4 h-4 bg-white rounded-full shadow transition-transform ${isPublic ? "translate-x-5" : "translate-x-1"}`} />
/> </div>
<span className="text-sm text-gray-700 dark:text-gray-300">Make my page public</span> <span className="text-sm text-gray-700 dark:text-gray-300">Make my page public</span>
</label> </label>
<button <button
onClick={saveProfile} onClick={saveProfile}
disabled={saving || !displayName || !slugValid} disabled={saving || !displayName || !slugValid}
className="w-full bg-pink-500 text-white rounded-xl py-2.5 font-medium text-sm disabled:opacity-50" className="w-full bg-rose-400 text-white rounded-xl py-2.5 font-medium text-sm disabled:opacity-50 active:scale-95 transition-transform"
> >
{saving ? "Saving..." : "Save Profile"} {saving ? "Saving" : "Save Profile"}
</button> </button>
{saveMsg && ( {saveMsg && (
<p className={`text-xs mt-2 text-center ${saveMsg.startsWith("Saved") ? "text-green-600" : "text-red-500"}`}> <p className={`text-xs mt-2 text-center ${saveMsg.startsWith("Saved") ? "text-green-600 dark:text-green-400" : "text-red-500"}`}>
{saveMsg} {saveMsg}
</p> </p>
)} )}
</div> </div>
{/* Products section */} {/* Products section */}
<div className="bg-white dark:bg-gray-800 rounded-2xl p-5 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>
<button <button
onClick={() => { resetProductForm(); setShowAddProduct(true); }} onClick={() => { resetProductForm(); setShowAddProduct(true); }}
className="text-sm text-pink-500 font-medium" className="text-sm text-rose-500 font-medium"
> >
+ Add + Add
</button> </button>
@ -224,26 +238,26 @@ export default function ProfileSettingsPage() {
{/* Add/Edit form */} {/* Add/Edit form */}
{showAddProduct && ( {showAddProduct && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-4 mb-4"> <div className="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-4 mb-4 space-y-2">
<input value={pTitle} onChange={e => setPTitle(e.target.value)} <input value={pTitle} onChange={e => setPTitle(e.target.value)}
placeholder="Product title *" className="w-full border dark:border-gray-600 rounded-lg px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-2" /> placeholder="Product title *" className={inputClass} />
<input value={pUrl} onChange={e => setPUrl(e.target.value)} <input value={pUrl} onChange={e => setPUrl(e.target.value)}
placeholder="Product URL *" className="w-full border dark:border-gray-600 rounded-lg px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-2" /> placeholder="Product URL *" className={inputClass} />
<textarea value={pDesc} onChange={e => setPDesc(e.target.value)} rows={2} <textarea value={pDesc} onChange={e => setPDesc(e.target.value)} rows={2}
placeholder="Description (optional)" className="w-full border dark:border-gray-600 rounded-lg px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-2 resize-none" /> placeholder="Description (optional)" className={`${inputClass} resize-none`} />
<input value={pImageUrl} onChange={e => setPImageUrl(e.target.value)} <input value={pImageUrl} onChange={e => setPImageUrl(e.target.value)}
placeholder="Image URL (optional)" className="w-full border dark:border-gray-600 rounded-lg px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-2" /> placeholder="Image URL (optional)" className={inputClass} />
<select value={pCategory} onChange={e => setPCategory(e.target.value)} <select value={pCategory} onChange={e => setPCategory(e.target.value)}
className="w-full border dark:border-gray-600 rounded-lg px-3 py-2 text-sm dark:bg-gray-700 dark:text-white mb-3"> className={inputClass}>
{CATEGORIES.map(c => <option key={c} value={c}>{CATEGORY_EMOJI[c]} {c.charAt(0).toUpperCase() + c.slice(1)}</option>)} {CATEGORIES.map(c => <option key={c} value={c}>{CATEGORY_EMOJI[c]} {c.charAt(0).toUpperCase() + c.slice(1)}</option>)}
</select> </select>
<div className="flex gap-2"> <div className="flex gap-2 pt-1">
<button onClick={saveProduct} disabled={!pTitle || !pUrl} <button onClick={saveProduct} disabled={!pTitle || !pUrl}
className="flex-1 bg-pink-500 text-white rounded-lg py-2 text-sm font-medium disabled:opacity-50"> className="flex-1 bg-rose-400 text-white rounded-xl py-2.5 text-sm font-medium disabled:opacity-50">
{editingProduct ? "Update" : "Add Product"} {editingProduct ? "Update" : "Add Product"}
</button> </button>
<button onClick={resetProductForm} <button onClick={resetProductForm}
className="flex-1 border dark:border-gray-600 rounded-lg py-2 text-sm text-gray-600 dark:text-gray-300"> className="flex-1 border border-gray-200 dark:border-gray-600 rounded-xl py-2.5 text-sm text-gray-600 dark:text-gray-300">
Cancel Cancel
</button> </button>
</div> </div>
@ -251,12 +265,16 @@ export default function ProfileSettingsPage() {
)} )}
{products.length === 0 && !showAddProduct && ( {products.length === 0 && !showAddProduct && (
<p className="text-sm text-gray-400 text-center py-4">No products yet. Add your first recommendation!</p> <div className="text-center py-8">
<span className="text-4xl">🛍</span>
<p className="text-sm text-gray-400 mt-2">No products yet.</p>
<p className="text-xs text-gray-400">Add your first recommendation!</p>
</div>
)} )}
<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 border dark:border-gray-700"> <div key={p.id} className="flex items-center gap-3 p-3 rounded-xl bg-gray-50 dark:bg-gray-700/50">
<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>
@ -264,11 +282,11 @@ export default function ProfileSettingsPage() {
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button onClick={() => moveProduct(p.id, "up")} disabled={i === 0} <button onClick={() => moveProduct(p.id, "up")} disabled={i === 0}
className="text-gray-400 hover:text-gray-600 disabled:opacity-30 text-xs px-1"></button> className="text-gray-400 disabled:opacity-30 text-xs px-1.5 py-1"></button>
<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 hover:text-gray-600 disabled:opacity-30 text-xs px-1"></button> className="text-gray-400 disabled:opacity-30 text-xs px-1.5 py-1"></button>
<button onClick={() => startEdit(p)} className="text-blue-400 hover:text-blue-600 text-xs px-1">Edit</button> <button onClick={() => startEdit(p)} className="text-rose-400 text-xs px-1.5 py-1">Edit</button>
<button onClick={() => deleteProduct(p.id)} className="text-red-400 hover:text-red-600 text-xs px-1"></button> <button onClick={() => deleteProduct(p.id)} className="text-gray-400 text-xs px-1.5 py-1"></button>
</div> </div>
</div> </div>
))} ))}