Files
reui-blocks/blocks/faq12.tsx
T

323 lines
11 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type Category = "Support" | "Account" | "Features" | "Security" | "Other";
interface FAQItem {
question: string;
answer: string;
category: Category;
}
const categoryLabels: Record<Category, string> = {
Support: "پشتیبانی",
Account: "حساب کاربری",
Features: "امکانات",
Security: "امنیت",
Other: "سایر",
};
const faqItems: FAQItem[] = [
{
category: "Support",
question: "آیا امکان استفاده رایگان وجود دارد؟",
answer:
"بله، نسخه رایگان با امکانات پایه در اختیار شما قرار دارد و در هر زمان می‌توانید متناسب با نیاز خود به پلن‌های حرفه‌ای ارتقا دهید.",
},
{
category: "Support",
question: "آیا پشتیبانی رایگان است یا باید همه چیز را خودم جستجو کنم؟",
answer:
"تیم پشتیبانی از طریق تیکت، ایمیل و مستندات در کنار شماست تا در کوتاه‌ترین زمان ممکن پاسخ‌گوی پرسش‌ها باشد.",
},
{
category: "Support",
question: "اگر کمک فوری نیاز داشته باشم چه؟",
answer:
"درخواست‌های فوری در سریع‌ترین زمان ممکن توسط تیم پشتیبانی بررسی و پاسخ داده می‌شوند.",
},
{
category: "Account",
question: "چگونه اطلاعات حساب کاربری را ویرایش کنم؟",
answer:
"از بخش تنظیمات حساب کاربری می‌توانید اطلاعات شخصی، رمز عبور و تنظیمات امنیتی را به‌سادگی ویرایش کنید.",
},
{
category: "Account",
question: "آیا ویرایش اطلاعات حساب کاربری آسان است؟",
answer: "بله، تنها با چند کلیک می‌توانید اطلاعات حساب خود را به‌روزرسانی کنید.",
},
{
category: "Account",
question: "اگر رمز عبورم را فراموش کنم چه می‌شود؟",
answer: "از گزینه «فراموشی رمز عبور» استفاده کنید تا لینک بازیابی برای شما ارسال شود.",
},
{
category: "Features",
question: "آیا قرار است همه چیز جایگزین هوش مصنوعی شود؟",
answer:
"هوش مصنوعی نقش دستیار را دارد و تصمیم‌های نهایی همچنان توسط کاربران انجام می‌شود.",
},
{
category: "Features",
question: "چه چیزی پلتفرم شما را منحصربه‌فرد می‌کند؟",
answer:
"ترکیب قابلیت‌های هوش مصنوعی، اتوماسیون و یکپارچه‌سازی، بهره‌وری سازمان را افزایش می‌دهد.",
},
{
category: "Features",
question: "آیا یکپارچه‌سازی با ابزارهای دیگر پشتیبانی می‌شود؟",
answer: "امکان اتصال به سرویس‌ها، APIها و ابزارهای متداول از طریق یکپارچه‌سازی فراهم است.",
},
{
category: "Security",
question: "داده‌های من چقدر امن هستند؟",
answer:
"داده‌ها با رمزنگاری، کنترل دسترسی و استانداردهای امنیتی محافظت می‌شوند.",
},
{
category: "Security",
question: "در صورت نشت داده چه می‌شود؟",
answer:
"در صورت بروز رخداد امنیتی، مطابق سیاست‌های اطلاع‌رسانی و مدیریت بحران اقدام خواهد شد.",
},
{
category: "Security",
question: "آیا سیستم پشتیبان دارید؟",
answer:
"نسخه‌های پشتیبان به‌صورت منظم و در زیرساخت‌های امن نگهداری می‌شوند.",
},
{
category: "Other",
question: "چرا قیمت‌گذاری‌تان این‌قدر پیچیده است؟",
answer:
"ساختار قیمت‌گذاری بر اساس امکانات، تعداد کاربران و نیازهای هر کسب‌وکار طراحی شده است.",
},
{
category: "Other",
question: "آیا بازپرداخت ارائه می‌دهید؟",
answer: "در صورت احراز شرایط مندرج در قوانین بازگشت وجه، امکان ثبت درخواست بازپرداخت وجود دارد.",
},
{
category: "Other",
question: "نقشه راه شما چطور است؟",
answer: "نقشه راه محصول به‌صورت مستمر بر اساس بازخورد کاربران و برنامه توسعه به‌روزرسانی می‌شود.",
},
];
const categories: Category[] = [
"Support",
"Account",
"Features",
"Security",
"Other",
];
const TOP_PADDING = 300;
interface Faq12Props {
className?: string;
}
const Faq12 = ({ className }: Faq12Props) => {
const [activeCategory, setActiveCategory] = useState<Category>("Support");
const observerRef = useRef<IntersectionObserver | null>(null);
const isScrollingRef = useRef(false);
const categoryRefs = useRef<Record<Category, HTMLDivElement | null>>({
Support: null,
Account: null,
Features: null,
Security: null,
Other: null,
});
const setupObserver = useCallback(() => {
observerRef.current?.disconnect();
let debounceTimeout: NodeJS.Timeout;
observerRef.current = new IntersectionObserver(
(entries) => {
if (isScrollingRef.current) return;
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(() => {
const intersectingEntries = entries.filter(
(entry) => entry.isIntersecting,
);
const entry = intersectingEntries.reduce(
(closest, current) => {
const rect = current.boundingClientRect;
const distanceFromThreshold = Math.abs(rect.top - TOP_PADDING);
const closestDistance = closest
? Math.abs(closest.boundingClientRect.top - TOP_PADDING)
: Infinity;
return distanceFromThreshold < closestDistance
? current
: closest;
},
null as IntersectionObserverEntry | null,
);
if (entry) {
const category = entry.target.getAttribute(
"data-category",
) as Category;
if (category) {
setActiveCategory(category);
}
}
}, 150);
},
{
root: null,
rootMargin: `-${TOP_PADDING}px 0px -100% 0px`,
threshold: [0, 0.25, 0.5, 0.75, 1],
},
);
Object.entries(categoryRefs.current).forEach(([category, element]) => {
if (element) {
element.setAttribute("data-category", category);
observerRef.current?.observe(element);
}
});
return () => {
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
};
}, []);
useEffect(() => {
const cleanup = setupObserver();
return () => {
cleanup();
observerRef.current?.disconnect();
};
}, [setupObserver]);
const handleCategoryClick = (category: Category) => {
setActiveCategory(category);
isScrollingRef.current = true;
const element = document.getElementById(`faq-${category.toLowerCase()}`);
if (element) {
element.style.scrollMargin = `${TOP_PADDING}px`;
element.scrollIntoView({ behavior: "smooth", block: "start" });
setTimeout(() => {
isScrollingRef.current = false;
}, 1000);
}
};
return (
<section
dir="rtl"
lang="fa"
className={cn(
"min-h-screen bg-[#F2F2F2] py-32 dark:bg-[#24242B]",
className,
)}
>
<div className="container mx-auto max-w-4xl">
<div className="text-center">
<h1 className="text-center text-xl font-semibold tracking-tight sm:text-2xll">
پاسخ پرسش‌های شما اینجاست
</h1>
<p className="mx-auto mt-4 max-w-xl text-center text-balance text-muted-foreground">
اگر پاسخ موردنظر خود را پیدا نکردید، می‌توانید از مستندات یا تیم پشتیبانی کمک بگیرید.
</p>
</div>
<div className="mx-auto mt-8 grid max-w-5xl gap-8 md:mt-12 md:grid-cols-[200px_1fr] md:gap-12 lg:mt-16">
<div className="sticky top-24 flex h-fit flex-col gap-4 max-md:hidden">
{categories.map((category) => (
<Button
variant="ghost"
key={category}
onClick={() => handleCategoryClick(category)}
className={`justify-start text-start text-xl transition-colors ${
activeCategory === category
? "font-semibold"
: "font-normal hover:opacity-75"
}`}
>
{categoryLabels[category]}
</Button>
))}
</div>
<div className="space-y-6 text-start">
{categories.map((category) => {
const categoryItems = faqItems.filter(
(item) => item.category === category,
);
return (
<div
key={category}
id={`faq-${category.toLowerCase()}`}
ref={(el) => {
categoryRefs.current[category] = el;
}}
className={cn(
"rounded-xl px-6",
activeCategory === category
? "bg-background"
: "bg-background md:bg-background/40",
)}
style={{
scrollMargin: `${TOP_PADDING}px`,
}}
>
<Accordion
type="single"
collapsible
defaultValue={`${categories[0]}-0`}
className="w-full"
>
{categoryItems.map((item, i) => (
<AccordionItem
key={i}
value={`${category}-${i}`}
className="border-b border-muted last:border-0"
>
<AccordionTrigger className="text-start text-base font-medium hover:no-underline">
{item.question}
</AccordionTrigger>
<AccordionContent className="text-base font-medium text-muted-foreground">
{item.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
);
})}
</div>
</div>
</div>
</section>
);
};
export { Faq12 };