React Code
React Code
function EditItemContent() {
const router = useRouter();
const params = useParams();
const searchParams = useSearchParams();
const { toast } = useToast();
const { startLoading, stopLoading } = usePageLoader();
useEffect(() => {
if (!sectionId || !itemId) {
toast({ variant: 'destructive', title: 'Error', description: 'Missing
section or item ID.' });
setIsFetching(false);
return;
}
setValue('dynamicFieldsConfig', updatedFields, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('dynamicFieldsConfig');
};
const handleRemoveDynamicField = async (id: string) => {
const currentFields = getValues('dynamicFieldsConfig') || [];
const updatedFields = currentFields.filter(f => f.id !== id);
setValue('dynamicFieldsConfig', updatedFields, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('dynamicFieldsConfig');
};
setValue('dynamicFieldsConfig', updatedFields, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('dynamicFieldsConfig');
};
setValue('faqs', updatedFaqs, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('faqs');
};
setValue('faqs', updatedFaqs, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('faqs');
};
setValue('faqs', updatedFaqs, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setHasUnsavedChanges(true);
await trigger('faqs');
};
return (
<div className="space-y-6 md:space-y-8 pb-4">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6 md:space-y-8
animate-slideInUp">
<Card className="shadow-lg">
<CardHeader><CardTitle className="text-xl md:text-2xl flex items-
center"><ImageIcon className="mr-3 h-5 w-5" /> Item
Details</CardTitle></CardHeader>
<CardContent className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-
4">
<div className="space-y-4">
<div>
<Label htmlFor="title">Item Title</Label>
<Input id="title" {...form.register('title')} placeholder="e.g.,
Valorant" disabled={isSaving} />
{errors.title && <p className="text-xs text-destructive mt-
1">{errors.title.message}</p>}
</div>
<div>
<Label htmlFor="category">Category</Label>
<Input id="category" {...form.register('category')}
placeholder="e.g., Tactical Shooter" disabled={isSaving} />
{errors.category && <p className="text-xs text-destructive mt-
1">{errors.category.message}</p>}
</div>
<div>
<Label htmlFor="order">Display Order</Label>
<Input id="order" type="number" {...form.register('order')}
placeholder="0" disabled={isSaving} />
<p className="text-xs text-muted-foreground pt-1">Lower numbers
appear first within the section.</p>
{errors.order && <p className="text-xs text-destructive mt-
1">{errors.order.message}</p>}
</div>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="imageSrc">Image URL</Label>
<Input id="imageSrc" type="url" {...form.register('imageSrc')}
placeholder="https://example.com/image.png" disabled={isSaving} />
{errors.imageSrc && <p className="text-xs text-destructive mt-
1">{errors.imageSrc.message}</p>}
</div>
{imageSrcPreview && (
<div className="mt-2">
<Label>Image Preview:</Label>
<div className="relative aspect-square w-full max-w-xs mt-1
border rounded-md overflow-hidden bg-muted">
<Image
src={imageSrcPreview}
alt={form.getValues('imageAlt') || 'Preview'}
fill
style={{objectFit: 'cover'}}
onError={() => toast({ variant: 'destructive', title:
'Invalid image URL' })}
/>
</div>
</div>
)}
</div>
<div className="md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-x-6
gap-y-4">
<div>
<Label htmlFor="imageAlt">Image Alt Text (Optional)</Label>
<Input id="imageAlt" {...form.register('imageAlt')}
placeholder="Descriptive alt text for accessibility" disabled={isSaving} />
</div>
<div>
<Label htmlFor="aiHint">AI Hint (Optional)</Label>
<Input id="aiHint" {...form.register('aiHint')}
placeholder="Keywords for AI image search" disabled={isSaving} />
<p className="text-xs text-muted-foreground pt-1">Max 2 keywords.
If blank, uses title.</p>
</div>
</div>
</CardContent>
</Card>
onCheckedChange={setCurrentDynamicFieldRequired}
disabled={isSaving}
/>
<Label htmlFor="dynamicFieldRequired">Required
Field</Label>
</div>
<Button
type="button"
size="sm"
onClick={handleAddDynamicField}
disabled={isSaving || !
currentDynamicFieldLabel.trim()}
>
<PlusCircle className="mr-2 h-4 w-4"/> Add
Field
</Button>
</div>
{dynamicFields.length > 0 && (
<div>
<h4 className="font-medium mb-2">Configured
Fields:</h4>
<div className="space-y-2">
{dynamicFields.map((field) => (
<DynamicFieldItem
key={field.id}
field={field}
isEditing={editingDynamicField ===
field.id}
onEdit={() =>
setEditingDynamicField(field.id)}
onCancel={() =>
setEditingDynamicField(null)}
onSave={(updates) => {
handleUpdateDynamicField(field.id,
updates);
setEditingDynamicField(null);
}}
onRemove={() =>
handleRemoveDynamicField(field.id)}
disabled={isSaving}
/>
))}
</div>
</div>
)}
</CardContent>
</AccordionContent>
</Card>
</AccordionItem>
<AccordionItem value="item-2" className="border-none">
<Card className="shadow-lg">
<AccordionTrigger className="hover:no-underline">
<CardHeader className="flex-1 text-left p-4 sm:p-6">
<CardTitle className="text-xl md:text-2xl flex items-
center"><MessageSquareMore className="mr-3 h-5 w-5"/> Item-Specific
FAQs</CardTitle>
<CardDescription className="pt-1">Add FAQs that will
appear only on this item's product page.</CardDescription>
</CardHeader>
</AccordionTrigger>
<AccordionContent>
<CardContent className="space-y-4 -mt-4">
<div className="space-y-4 p-4 border rounded-md bg-
muted/50">
<h4 className="font-medium">Add New FAQ</h4>
<div>
<Label
htmlFor="newFaqQuestion">Question</Label>
<Input
id="newFaqQuestion"
value={newFaqQuestion}
onChange={e =>
setNewFaqQuestion(e.target.value)}
placeholder="e.g., How to claim bonus?"
disabled={isSaving}
/>
</div>
<div>
<Label htmlFor="newFaqAnswer">Answer</Label>
<Textarea
id="newFaqAnswer"
value={newFaqAnswer}
onChange={e =>
setNewFaqAnswer(e.target.value)}
placeholder="Detailed answer..."
rows={3}
disabled={isSaving}
/>
</div>
<div>
<Label htmlFor="newFaqImageUrl">Image URL
(Optional)</Label>
<Input
id="newFaqImageUrl"
type="url"
value={newFaqImageUrl}
onChange={e =>
setNewFaqImageUrl(e.target.value)}
placeholder="https://example.com/faq-
image.png"
disabled={isSaving}
/>
</div>
<Button
type="button"
size="sm"
onClick={handleAddFaq}
disabled={isSaving || !newFaqQuestion.trim() || !
newFaqAnswer.trim()}
>
<PlusCircle className="mr-2 h-4 w-4"/> Add FAQ
</Button>
</div>
{faqs.length > 0 && (
<div>
<h4 className="font-medium mb-2">Configured
FAQs:</h4>
<div className="space-y-2">
{faqs.map((faq) => (
<FaqItem
key={faq.id}
faq={faq}
isEditing={editingFaq === faq.id}
onEdit={() => setEditingFaq(faq.id)}
onCancel={() => setEditingFaq(null)}
onSave={(updates) => {
handleUpdateFaq(faq.id, updates);
setEditingFaq(null);
}}
onRemove={() =>
handleRemoveFaq(faq.id)}
disabled={isSaving}
/>
))}
</div>
</div>
)}
</CardContent>
</AccordionContent>
</Card>
</AccordionItem>
</Accordion>
<Button
type="submit"
disabled={!canSave}
size="lg"
className="w-full"
>
{isSaving ? <RefreshCw className="mr-2 h-4 w-4 animate-spin" /> : <Save
className="mr-2 h-4 w-4" />}
{isSaving ? 'Saving Changes...' : 'Save All Changes'}
</Button>
onSave({
label: editLabel.trim(),
placeholder: editPlaceholder.trim() || undefined,
type: editType,
required: editRequired,
...(maxLengthValue && maxLengthValue > 0 && { maxLength: maxLengthValue })
});
};
if (isEditing) {
return (
<div className="p-3 border rounded-md bg-card shadow-sm space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<Label>Field Label</Label>
<Input
value={editLabel}
onChange={e => setEditLabel(e.target.value)}
disabled={disabled}
size={undefined}
/>
</div>
<div>
<Label>Field Type</Label>
<Select value={editType} onValueChange={(value) => setEditType(value as
DynamicFieldConfig['type'])} disabled={disabled}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="text">Text</SelectItem>
<SelectItem value="email">Email</SelectItem>
<SelectItem value="number">Number</SelectItem>
<SelectItem value="alphabetical">Alphabetical</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label>Placeholder</Label>
<Input
value={editPlaceholder}
onChange={e => setEditPlaceholder(e.target.value)}
disabled={disabled}
size={undefined}
/>
</div>
{isMaxLengthApplicable && (
<div>
<Label>Max Length</Label>
<Input
type="number"
value={editMaxLength}
onChange={e => setEditMaxLength(e.target.value)}
disabled={disabled}
min="1"
size={undefined}
/>
</div>
)}
<div className="flex items-center space-x-2">
<Switch
checked={editRequired}
onCheckedChange={setEditRequired}
disabled={disabled}
/>
<Label>Required Field</Label>
</div>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline" size="sm" onClick={onCancel}
disabled={disabled}>
<X className="mr-1 h-3 w-3"/> Cancel
</Button>
<Button type="button" size="sm" onClick={handleSave} disabled={disabled
|| !editLabel.trim()}>
<Check className="mr-1 h-3 w-3"/> Save
</Button>
</div>
</div>
);
}
return (
<div className="flex items-center justify-between p-3 border rounded-md bg-card
shadow-sm text-sm">
<div>
<p><span className="font-semibold">{field.label}</span> ({field.type})</p>
<p className="text-xs text-muted-foreground">
Required: {field.required ? 'Yes' : 'No'} | Max Length: {field.maxLength
|| 'N/A'}
{field.placeholder && ` | Placeholder: "${field.placeholder}"`}
</p>
</div>
<div className="flex space-x-1">
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onEdit}
disabled={disabled}
>
<Edit3 className="h-4 w-4"/>
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onRemove}
disabled={disabled}
>
<Trash2 className="h-4 w-4 text-destructive"/>
</Button>
</div>
</div>
);
}
onSave({
question: editQuestion.trim(),
answer: editAnswer.trim(),
imageUrl: editImageUrl.trim() || undefined,
});
};
if (isEditing) {
return (
<div className="bg-card border rounded-md p-4 shadow-sm space-y-3">
<div>
<Label>Question</Label>
<Input
value={editQuestion}
onChange={e => setEditQuestion(e.target.value)}
disabled={disabled}
size={undefined}
/>
</div>
<div>
<Label>Answer</Label>
<Textarea
value={editAnswer}
onChange={e => setEditAnswer(e.target.value)}
rows={3}
disabled={disabled}
/>
</div>
<div>
<Label>Image URL (Optional)</Label>
<Input
type="url"
value={editImageUrl}
onChange={e => setEditImageUrl(e.target.value)}
disabled={disabled}
size={undefined}
/>
</div>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline" size="sm" onClick={onCancel}
disabled={disabled}>
<X className="mr-1 h-3 w-3"/> Cancel
</Button>
<Button
type="button"
size="sm"
onClick={handleSave}
disabled={disabled || !editQuestion.trim() || !editAnswer.trim()}
>
<Check className="mr-1 h-3 w-3"/> Save
</Button>
</div>
</div>
);
}
return (
<AccordionItem value={faq.id} className="bg-card border rounded-md px-1 shadow-
sm">
<div className="flex items-center justify-between group p-1">
<AccordionTrigger className="text-left font-semibold hover:no-underline px-
3 py-2 text-sm flex-1">
{faq.question}
</AccordionTrigger>
<div className="flex space-x-1 opacity-50 group-hover:opacity-100">
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
onEdit();
}}
disabled={disabled}
className="h-8 w-8"
>
<Edit3 className="h-4 w-4"/>
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
disabled={disabled}
className="h-8 w-8"
>
<Trash2 className="h-4 w-4 text-destructive"/>
</Button>
</div>
</div>
<AccordionContent className="px-4 pb-3 pt-0 text-sm text-muted-foreground">
<p className="whitespace-pre-line">{faq.answer}</p>
{faq.imageUrl && <p className="mt-1 text-xs">Image: {faq.imageUrl}</p>}
</AccordionContent>
</AccordionItem>
);
}