187 lines
5.2 KiB
TypeScript
187 lines
5.2 KiB
TypeScript
import { BytescaleUploader } from '@/components/BytescaleUploader/BytescaleUploader';
|
||
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { Textarea } from '@/components/ui/textarea';
|
||
|
||
import { X } from 'lucide-react';
|
||
|
||
interface SeoData {
|
||
seoTitle?: string;
|
||
metaDescription?: string;
|
||
focusKeyphrase?: string;
|
||
tags?: string[];
|
||
ogTitle?: string;
|
||
ogDescription?: string;
|
||
ogImage?: string;
|
||
}
|
||
|
||
interface SeoFieldsProps {
|
||
value?: SeoData;
|
||
onChange: (seo: SeoData) => void;
|
||
slug?: string;
|
||
folderPath?: '/seo';
|
||
}
|
||
|
||
export default function SeoFields({ value, onChange, slug, folderPath = '/seo' }: SeoFieldsProps) {
|
||
const seo = value || {};
|
||
|
||
const updateSeo = (field: keyof SeoData, fieldValue: any) => {
|
||
onChange({
|
||
...seo,
|
||
[field]: fieldValue,
|
||
});
|
||
};
|
||
|
||
const removeTag = (index: number) => {
|
||
updateSeo(
|
||
'tags',
|
||
(seo.tags || []).filter((_, i) => i !== index)
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-5 p-5 border rounded-xl bg-muted/20">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="text-lg font-bold">SEO Settings</h3>
|
||
|
||
<p className="text-sm text-muted-foreground">Optimize for Google & social sharing</p>
|
||
</div>
|
||
|
||
<Badge variant="secondary">Optional</Badge>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between">
|
||
<Label className="text-sm font-semibold">SEO Title</Label>
|
||
|
||
<span className="text-xs text-muted-foreground">{seo.seoTitle?.length || 0}/60</span>
|
||
</div>
|
||
|
||
<Input
|
||
placeholder="Best Health Checkup Package in Kochi"
|
||
value={seo.seoTitle || ''}
|
||
onChange={(e) => updateSeo('seoTitle', e.target.value)}
|
||
/>
|
||
|
||
<p className="text-xs text-muted-foreground">Recommended: 50–60 characters</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between">
|
||
<Label className="text-sm font-semibold">Meta Description</Label>
|
||
|
||
<span className="text-xs text-muted-foreground">{seo.metaDescription?.length || 0}/160</span>
|
||
</div>
|
||
|
||
<Textarea
|
||
rows={4}
|
||
placeholder="Short description shown in Google search results"
|
||
value={seo.metaDescription || ''}
|
||
onChange={(e) => updateSeo('metaDescription', e.target.value)}
|
||
/>
|
||
|
||
<p className="text-xs text-muted-foreground">Recommended: 150–160 characters</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-semibold">Focus Keyphrase</Label>
|
||
|
||
<Input
|
||
placeholder="health checkup package kochi"
|
||
value={seo.focusKeyphrase || ''}
|
||
onChange={(e) => updateSeo('focusKeyphrase', e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-semibold">Tags / Keywords</Label>
|
||
|
||
<div className="flex flex-wrap gap-2 border rounded-md p-3 min-h-[48px] bg-background">
|
||
{(seo.tags || []).map((tag, index) => (
|
||
<div
|
||
key={index}
|
||
className="bg-primary/10 text-primary px-3 py-1 rounded-full text-sm flex items-center gap-2"
|
||
>
|
||
<span>{tag}</span>
|
||
|
||
<button type="button" onClick={() => removeTag(index)} className="hover:text-red-500 transition-colors">
|
||
<X className="h-3 w-3" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
|
||
<Input
|
||
placeholder="Type keyword and press Enter"
|
||
className="border-0 shadow-none focus-visible:ring-0 min-w-[220px] flex-1"
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' && e.currentTarget.value.trim()) {
|
||
e.preventDefault();
|
||
|
||
const newTag = e.currentTarget.value.trim();
|
||
|
||
if (!(seo.tags || []).includes(newTag)) {
|
||
updateSeo('tags', [...(seo.tags || []), newTag]);
|
||
}
|
||
|
||
e.currentTarget.value = '';
|
||
}
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
<p className="text-xs text-muted-foreground">Press Enter to add tags</p>
|
||
</div>
|
||
|
||
<div className="border-t pt-5 space-y-5">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h4 className="font-bold">Open Graph (Social Preview)</h4>
|
||
|
||
<p className="text-sm text-muted-foreground">Facebook, WhatsApp & Twitter sharing</p>
|
||
</div>
|
||
|
||
<Badge variant="secondary">Optional</Badge>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-semibold">OG Title</Label>
|
||
|
||
<Input
|
||
placeholder="Title for social sharing"
|
||
value={seo.ogTitle || ''}
|
||
onChange={(e) => updateSeo('ogTitle', e.target.value)}
|
||
/>
|
||
|
||
<p className="text-xs text-muted-foreground">If empty, SEO title will be used</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-semibold">OG Description</Label>
|
||
|
||
<Textarea
|
||
rows={4}
|
||
placeholder="Description for social sharing"
|
||
value={seo.ogDescription || ''}
|
||
onChange={(e) => updateSeo('ogDescription', e.target.value)}
|
||
/>
|
||
|
||
<p className="text-xs text-muted-foreground">If empty, meta description will be used</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-semibold">OG Image</Label>
|
||
|
||
<BytescaleUploader
|
||
value={seo.ogImage || ''}
|
||
folderPath={folderPath}
|
||
onChange={(url) => updateSeo('ogImage', url)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|