Initial commit - uploading project files

This commit is contained in:
fatemeh sane
2026-09-06 09:42:20 +03:30
commit 67d6797a26
48 changed files with 7768 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
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>);
}