Files
DashboardAramesh/src/components/SeoPanel.tsx
T

137 lines
4.7 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 React from 'react';
import {
CheckCircle2Icon,
CircleAlertIcon,
GlobeIcon,
KeyIcon,
SearchCheckIcon,
TextIcon } from
'lucide-react';
import { SurfaceCard } from './ui/SurfaceCard';
import { TextField } from './ui/TextField';
import { TextArea } from './ui/TextArea';
export interface SeoState {
focusKeyword: string;
seoTitle: string;
metaDescription: string;
slug: string;
}
interface SeoPanelProps {
value: SeoState;
onChange: (value: SeoState) => void;
}
interface Check {
label: string;
passed: boolean;
}
export function SeoPanel({ value, onChange }: SeoPanelProps) {
const set = (key: keyof SeoState, next: string) => onChange({ ...value, [key]: next });
const checks: Check[] = [
{
label: 'کلمه کلیدی در عنوان سئو آمده است',
passed: Boolean(value.focusKeyword) && value.seoTitle.includes(value.focusKeyword)
},
{
label: 'طول عنوان سئو بین ۳۰ تا ۶۰ نویسه است',
passed: value.seoTitle.length >= 30 && value.seoTitle.length <= 60
},
{
label: 'توضیح متا بین ۷۰ تا ۱۶۰ نویسه است',
passed: value.metaDescription.length >= 70 && value.metaDescription.length <= 160
},
{
label: 'کلمه کلیدی در توضیح متا آمده است',
passed: Boolean(value.focusKeyword) && value.metaDescription.includes(value.focusKeyword)
},
{ label: 'نامک (Slug) کوتاه و خواناست', passed: value.slug.length > 0 && value.slug.length <= 60 }];
const passedCount = checks.filter((check) => check.passed).length;
const score = Math.round(passedCount / checks.length * 100);
const scoreLabel = score >= 80 ? 'عالی' : score >= 50 ? 'قابل بهبود' : 'ضعیف';
return (
<SurfaceCard
title="تنظیمات سئو"
description="پیش از انتشار، امتیاز سئوی مقاله را بررسی کنید."
icon={SearchCheckIcon}
actions={
<span
className={`rounded-full px-4 py-1.5 text-xs ${
score >= 80 ? 'bg-copper text-[#241a11]' : 'bg-copper-soft text-copper'}`
}>
امتیاز سئو: {score} — {scoreLabel}
</span>
}>
<div className="grid gap-8 xl:grid-cols-[1.1fr_1fr]">
<div className="space-y-6">
<TextField
label="کلمه کلیدی اصلی"
icon={KeyIcon}
value={value.focusKeyword}
onChange={(event) => set('focusKeyword', event.target.value)}
placeholder="سرمایه‌گذاری ملکی" />
<TextField
label="عنوان سئو"
icon={TextIcon}
value={value.seoTitle}
onChange={(event) => set('seoTitle', event.target.value)}
hint={`${value.seoTitle.length} از ۶۰ نویسه`} />
<TextField
label="نامک (Slug)"
icon={GlobeIcon}
value={value.slug}
onChange={(event) => set('slug', event.target.value)}
placeholder="real-estate-investment-guide" />
<TextArea
label="توضیح متا"
rows={3}
value={value.metaDescription}
onChange={(event) => set('metaDescription', event.target.value)}
hint={`${value.metaDescription.length} از ۱۶۰ نویسه`} />
</div>
<div className="space-y-6">
<div className="rounded-2xl border border-white/[0.07] bg-ink p-6">
<p className="text-xs text-ivory/40">پیش‌نمایش نتیجه گوگل</p>
<div className="mt-4" dir="ltr">
<p className="text-xs text-[#9aa0a6]">
arameshplaza.com › blog › {value.slug || 'article'}
</p>
<p className="mt-1.5 text-lg leading-7 text-[#8ab4f8]" dir="rtl">
{value.seoTitle || 'عنوان سئو را وارد کنید'}
</p>
<p className="mt-1.5 text-sm leading-6 text-[#bdc1c6]" dir="rtl">
{value.metaDescription || 'توضیح متا اینجا نمایش داده می‌شود.'}
</p>
</div>
</div>
<ul className="flex flex-col gap-3">
{checks.map((check) =>
<li key={check.label} className="flex items-start gap-3 text-sm">
{check.passed ?
<CheckCircle2Icon className="mt-0.5 h-4 w-4 shrink-0 text-copper" /> :
<CircleAlertIcon className="mt-0.5 h-4 w-4 shrink-0 text-ivory/30" />
}
<span className={check.passed ? 'text-ivory/80' : 'text-ivory/40'}>{check.label}</span>
</li>
)}
</ul>
</div>
</div>
</SurfaceCard>);
}