Files
gg-backend/frontend/src/components/SeoFields/SeoFields.tsx
T
2026-05-26 15:48:01 +05:30

187 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: 5060 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: 150160 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>
);
}