323 lines
11 KiB
TypeScript
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 };
|