feat(admin): merge Site Settings and Wishlist into single Configurações panel

Single-wishlist apps no longer need two separate edit sections.
Both siteTitle/homepageSubtext and wishlist fields now save together
in one action from a unified Configurações card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano Belisario
2026-05-03 21:10:58 +00:00
parent 21e8b7e137
commit 3df9a67b97
5 changed files with 126 additions and 64 deletions

View File

@@ -14,7 +14,6 @@ import {
} from '@/lib/api';
import Header from '@/components/header';
import Link from 'next/link';
import SettingsSection from '@/components/admin/SettingsSection';
import ItemCard from '@/components/admin/ItemCard';
import ItemForm from '@/components/admin/ItemForm';
import ImageUpload from '@/components/image-upload';
@@ -42,7 +41,7 @@ function AdminPageContent() {
homepageSubtext: '',
});
// Wishlist edit state
// Unified config edit state
const [isEditingWishlist, setIsEditingWishlist] = useState(false);
const [editForm, setEditForm] = useState({
name: '',
@@ -51,6 +50,8 @@ function AdminPageContent() {
preferences: '',
imageUrl: '',
isPublic: true,
siteTitle: '',
homepageSubtext: '',
});
const [editError, setEditError] = useState('');
const [isWishlistImageUploading, setIsWishlistImageUploading] = useState(false);
@@ -105,6 +106,8 @@ function AdminPageContent() {
preferences: wishlist.preferences || '',
imageUrl: wishlist.imageUrl || '',
isPublic: wishlist.isPublic,
siteTitle: settings.siteTitle,
homepageSubtext: settings.homepageSubtext,
});
setEditError('');
setIsEditingWishlist(true);
@@ -115,11 +118,16 @@ function AdminPageContent() {
if (!wishlist) return;
setEditError('');
try {
const updated = await wishlistsApi.update(wishlist.id, editForm);
const { siteTitle, homepageSubtext, ...wishlistFields } = editForm;
const [updated] = await Promise.all([
wishlistsApi.update(wishlist.id, wishlistFields),
settingsApi.updateSettings({ siteTitle, homepageSubtext }),
]);
setWishlist(updated);
setSettings({ siteTitle, homepageSubtext });
setIsEditingWishlist(false);
} catch (error: any) {
setEditError(error.message || 'Failed to update wishlist');
setEditError(error.message || 'Failed to update settings');
}
};
@@ -181,10 +189,6 @@ function AdminPageContent() {
}
};
const handleUpdateSettings = async (updatedSettings: Settings) => {
await settingsApi.updateSettings(updatedSettings);
setSettings(updatedSettings);
};
const editingItem = items.find((item) => item.id === editingItemId);
@@ -229,55 +233,72 @@ function AdminPageContent() {
</div>
) : (
<>
{/* Settings Section */}
<SettingsSection settings={settings} onUpdate={handleUpdateSettings} />
{/* Wishlist Info Section */}
{/* Unified Settings & Wishlist Config */}
{wishlist && (
<div className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="p-5 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Configurações</h2>
{!isEditingWishlist && (
<button
onClick={startEditingWishlist}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm cursor-pointer"
>
Editar
</button>
)}
</div>
<div className="p-5">
{isEditingWishlist ? (
<form onSubmit={handleUpdateWishlist} className="space-y-4">
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
Editar Lista
</h2>
{editError && (
<div className="p-3 bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-400 rounded-lg text-base">
{editError}
</div>
)}
<ImageUpload
currentImageUrl={editForm.imageUrl}
onImageChange={(url) =>
setEditForm((prev) => ({ ...prev, imageUrl: url }))
}
onUploadStateChange={setIsWishlistImageUploading}
type="wishlist"
label="Imagem da Lista"
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Título do site *
</label>
<input
type="text"
required
value={editForm.siteTitle}
onChange={(e) => setEditForm((prev) => ({ ...prev, siteTitle: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Nome da lista *
</label>
<input
type="text"
required
value={editForm.name}
onChange={(e) => setEditForm((prev) => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Nome *
Mensagem de boas-vindas
</label>
<input
type="text"
required
value={editForm.name}
onChange={(e) =>
setEditForm((prev) => ({ ...prev, name: e.target.value }))
}
<textarea
value={editForm.homepageSubtext}
onChange={(e) => setEditForm((prev) => ({ ...prev, homepageSubtext: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
rows={2}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Descrição
Descrição da lista
</label>
<textarea
value={editForm.description}
onChange={(e) =>
setEditForm((prev) => ({ ...prev, description: e.target.value }))
}
onChange={(e) => setEditForm((prev) => ({ ...prev, description: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white"
rows={2}
/>
@@ -288,12 +309,17 @@ function AdminPageContent() {
</label>
<RichTextEditor
value={editForm.preferences}
onChange={(html) =>
setEditForm((prev) => ({ ...prev, preferences: html }))
}
onChange={(html) => setEditForm((prev) => ({ ...prev, preferences: html }))}
placeholder="Interesses e preferências gerais..."
/>
</div>
<ImageUpload
currentImageUrl={editForm.imageUrl}
onImageChange={(url) => setEditForm((prev) => ({ ...prev, imageUrl: url }))}
onUploadStateChange={setIsWishlistImageUploading}
type="wishlist"
label="Imagem da Lista"
/>
<div className="flex gap-3 justify-end">
<button
type="button"
@@ -312,33 +338,38 @@ function AdminPageContent() {
</div>
</form>
) : (
<div className="flex items-start gap-4">
{wishlist.imageUrl && (
<img
src={wishlist.imageUrl}
alt={wishlist.name}
className="w-20 h-20 object-cover rounded border border-gray-200 dark:border-gray-600 flex-shrink-0"
/>
)}
<div className="flex-1">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
{wishlist.name}
</h2>
{wishlist.description && (
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
{wishlist.description}
</p>
<div className="space-y-3">
<div className="flex items-start gap-4">
{wishlist.imageUrl && (
<img
src={wishlist.imageUrl}
alt={wishlist.name}
className="w-16 h-16 object-cover rounded border border-gray-200 dark:border-gray-600 flex-shrink-0"
/>
)}
<p className="text-sm text-gray-500 dark:text-gray-500 mt-1">
/{wishlist.slug}
</p>
<div className="flex-1 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-2">
<div>
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">Título do site</p>
<p className="text-sm text-gray-900 dark:text-white">{settings.siteTitle}</p>
</div>
<div>
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">Nome da lista</p>
<p className="text-sm text-gray-900 dark:text-white">{wishlist.name}</p>
</div>
{settings.homepageSubtext && (
<div className="md:col-span-2">
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">Mensagem de boas-vindas</p>
<p className="text-sm text-gray-900 dark:text-white">{settings.homepageSubtext}</p>
</div>
)}
{wishlist.description && (
<div className="md:col-span-2">
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">Descrição</p>
<p className="text-sm text-gray-900 dark:text-white">{wishlist.description}</p>
</div>
)}
</div>
</div>
<button
onClick={startEditingWishlist}
className="flex-shrink-0 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm cursor-pointer"
>
Editar
</button>
</div>
)}
</div>

View File

@@ -81,6 +81,8 @@ export async function PATCH(
const {
name,
description,
price,
currency,
quantity,
imageUrl,
purchaseUrls,
@@ -108,6 +110,8 @@ export async function PATCH(
if (name !== undefined) updateData.name = name;
if (description !== undefined) updateData.description = description;
if (price !== undefined) updateData.price = price != null ? Number(price) : null;
if (currency !== undefined) updateData.currency = currency;
if (quantity !== undefined) updateData.quantity = quantity;
if (imageUrl !== undefined) updateData.imageUrl = imageUrl;
if (purchaseUrls !== undefined) updateData.purchaseUrls = purchaseUrls;

View File

@@ -82,6 +82,8 @@ export async function POST(
const {
name,
description,
price,
currency,
quantity,
imageUrl,
purchaseUrls,
@@ -126,6 +128,8 @@ export async function POST(
wishlistId: id,
name,
description: description || null,
price: price != null ? Number(price) : null,
currency: currency || 'BRL',
quantity: quantity || 1,
imageUrl: imageUrl || null,
purchaseUrls: purchaseUrls || null,