Files
gg-backend/frontend/src/components/SeoFields/SeoFields.tsx
T

187 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-05-26 15:48:01 +05:30
import { BytescaleUploader } from '@/components/BytescaleUploader/BytescaleUploader';
2026-05-26 11:56:22 +05:30
2026-05-26 15:48:01 +05:30
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
2026-05-26 11:56:22 +05:30
2026-05-26 15:48:01 +05:30
import { X } from 'lucide-react';
2026-05-26 11:56:22 +05:30
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;
2026-05-26 15:48:01 +05:30
folderPath?: '/seo';
2026-05-26 11:56:22 +05:30
}
2026-05-26 15:48:01 +05:30
export default function SeoFields({ value, onChange, slug, folderPath = '/seo' }: SeoFieldsProps) {
2026-05-26 11:56:22 +05:30
const seo = value || {};
const updateSeo = (field: keyof SeoData, fieldValue: any) => {
onChange({
...seo,
[field]: fieldValue,
});
};
const removeTag = (index: number) => {
updateSeo(
2026-05-26 15:48:01 +05:30
'tags',
(seo.tags || []).filter((_, i) => i !== index)
2026-05-26 11:56:22 +05:30
);
};
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>
2026-05-26 15:48:01 +05:30
<p className="text-sm text-muted-foreground">Optimize for Google & social sharing</p>
2026-05-26 11:56:22 +05:30
</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>
2026-05-26 15:48:01 +05:30
<span className="text-xs text-muted-foreground">{seo.seoTitle?.length || 0}/60</span>
2026-05-26 11:56:22 +05:30
</div>
<Input
placeholder="Best Health Checkup Package in Kochi"
2026-05-26 15:48:01 +05:30
value={seo.seoTitle || ''}
onChange={(e) => updateSeo('seoTitle', e.target.value)}
2026-05-26 11:56:22 +05:30
/>
2026-05-26 15:48:01 +05:30
<p className="text-xs text-muted-foreground">Recommended: 5060 characters</p>
2026-05-26 11:56:22 +05:30
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-sm font-semibold">Meta Description</Label>
2026-05-26 15:48:01 +05:30
<span className="text-xs text-muted-foreground">{seo.metaDescription?.length || 0}/160</span>
2026-05-26 11:56:22 +05:30
</div>
<Textarea
rows={4}
placeholder="Short description shown in Google search results"
2026-05-26 15:48:01 +05:30
value={seo.metaDescription || ''}
onChange={(e) => updateSeo('metaDescription', e.target.value)}
2026-05-26 11:56:22 +05:30
/>
2026-05-26 15:48:01 +05:30
<p className="text-xs text-muted-foreground">Recommended: 150160 characters</p>
2026-05-26 11:56:22 +05:30
</div>
<div className="space-y-2">
<Label className="text-sm font-semibold">Focus Keyphrase</Label>
<Input
placeholder="health checkup package kochi"
2026-05-26 15:48:01 +05:30
value={seo.focusKeyphrase || ''}
onChange={(e) => updateSeo('focusKeyphrase', e.target.value)}
2026-05-26 11:56:22 +05:30
/>
</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>
2026-05-26 15:48:01 +05:30
<button type="button" onClick={() => removeTag(index)} className="hover:text-red-500 transition-colors">
2026-05-26 11:56:22 +05:30
<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) => {
2026-05-26 15:48:01 +05:30
if (e.key === 'Enter' && e.currentTarget.value.trim()) {
2026-05-26 11:56:22 +05:30
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!(seo.tags || []).includes(newTag)) {
2026-05-26 15:48:01 +05:30
updateSeo('tags', [...(seo.tags || []), newTag]);
2026-05-26 11:56:22 +05:30
}
2026-05-26 15:48:01 +05:30
e.currentTarget.value = '';
2026-05-26 11:56:22 +05:30
}
}}
/>
</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>
2026-05-26 15:48:01 +05:30
<p className="text-sm text-muted-foreground">Facebook, WhatsApp & Twitter sharing</p>
2026-05-26 11:56:22 +05:30
</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"
2026-05-26 15:48:01 +05:30
value={seo.ogTitle || ''}
onChange={(e) => updateSeo('ogTitle', e.target.value)}
2026-05-26 11:56:22 +05:30
/>
2026-05-26 15:48:01 +05:30
<p className="text-xs text-muted-foreground">If empty, SEO title will be used</p>
2026-05-26 11:56:22 +05:30
</div>
<div className="space-y-2">
<Label className="text-sm font-semibold">OG Description</Label>
<Textarea
rows={4}
placeholder="Description for social sharing"
2026-05-26 15:48:01 +05:30
value={seo.ogDescription || ''}
onChange={(e) => updateSeo('ogDescription', e.target.value)}
2026-05-26 11:56:22 +05:30
/>
2026-05-26 15:48:01 +05:30
<p className="text-xs text-muted-foreground">If empty, meta description will be used</p>
2026-05-26 11:56:22 +05:30
</div>
<div className="space-y-2">
<Label className="text-sm font-semibold">OG Image</Label>
<BytescaleUploader
2026-05-26 15:48:01 +05:30
value={seo.ogImage || ''}
2026-05-26 11:56:22 +05:30
folderPath={folderPath}
2026-05-26 15:48:01 +05:30
onChange={(url) => updateSeo('ogImage', url)}
2026-05-26 11:56:22 +05:30
/>
</div>
</div>
</div>
);
}