Files

652 lines
19 KiB
TypeScript
Raw Permalink 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.
"use client";
import type { LucideIcon } from "lucide-react";
import {
ChevronLeftIcon,
Chrome,
LayoutGrid,
Leaf,
Lock,
Puzzle,
Shield,
Sparkles,
Star,
User,
Workflow,
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
interface IconListItem {
icon: LucideIcon;
title: string;
}
interface StepDef {
title: string;
description: string | IconListItem[];
className?: string;
isBlocking?: boolean;
component: React.ComponentType<StepComponentProps>;
secondaryCta?: React.ComponentType;
cta?: React.ComponentType<StepCtaProps>;
}
const Background = () => {
return (
<div className="flex h-[100dvh] w-full items-start">
<div className="h-full w-64 shrink-0 border-r">
<div className="flex h-14 w-full items-center border-b p-4">
<img
src="/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo-word.png"
className="w-40 dark:invert"
alt="logo"
/>
</div>
<ul className="space-y-2 p-4">
{Array.from({ length: 5 }).map((_, index) => {
return (
<li
key={`dummy-link-${index}`}
className="block h-8 w-50 rounded-md bg-muted"
/>
);
})}
</ul>
</div>
<div className="flex h-14 w-full items-center border-b p-4">
<span className="block h-8 w-full max-w-50 rounded-md bg-muted" />
</div>
</div>
);
};
interface StepIndicatorProps {
totalSteps: number;
currentStep: number;
}
const StepIndicator = ({ totalSteps, currentStep }: StepIndicatorProps) => {
return (
<div className="flex items-center justify-center gap-2">
{Array.from({ length: totalSteps }).map((_, i) => {
return (
<span
key={`step-indicator-${i}`}
className={cn(
"block size-2 rounded-full transition-colors",
currentStep >= i ? "bg-primary" : "border bg-muted",
)}
/>
);
})}
</div>
);
};
interface FadeContainerProps {
children: React.ReactNode;
condition: boolean;
className?: string;
}
const FadeContainer = ({
children,
condition,
className,
}: FadeContainerProps) => {
const fadeVariants = {
initial: {
opacity: 0,
filter: "blur(4px)",
},
animate: {
opacity: 1,
filter: "blur(0px)",
},
exit: {
opacity: 0,
filter: "blur(4px)",
},
};
return (
<AnimatePresence mode="popLayout">
{condition && (
<motion.div
variants={fadeVariants}
initial="initial"
animate="animate"
exit="exit"
className={className}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
interface StepDescriptionCardProps {
title: string;
description: string | IconListItem[];
}
const StepDescriptionCard = ({
title,
description,
}: StepDescriptionCardProps) => {
return (
<div className="space-y-2 md:space-y-4">
<h3 className="text-xl font-semibold md:text-xl">{title}</h3>
<div className="text-sm leading-tight text-muted-foreground md:text-base">
{typeof description === "string" ? (
<p>{description}</p>
) : (
<ul className="space-y-4">
{description?.map((item, i) => {
return (
<li key={`step-${i}`} className="flex items-center gap-2">
<item.icon className="size-4 shrink-0" />
<p>{item.title}</p>
</li>
);
})}
</ul>
)}
</div>
</div>
);
};
interface StepComponentProps {
stepIndex: number;
onValidationChange: (stepIndex: number, isValid: boolean) => void;
}
interface StepCtaProps {
onClick: () => void;
disabled?: boolean;
}
const StepOneComponent = () => {
return (
<form
onSubmit={(e) => e.preventDefault()}
className="grid grid-cols-2 gap-2 md:gap-4"
>
<div className="space-y-2">
<Label>نام</Label>
<Input type="text" placeholder="نام" className="bg-background" />
</div>
<div className="space-y-2">
<Label>نام خانوادگی</Label>
<Input type="text" placeholder="نام خانوادگی" className="bg-background" />
</div>
<div className="col-span-2 space-y-2">
<Label>کجا کار میکنید؟</Label>
<Input type="text" placeholder="رویش مارکت" className="bg-background" />
</div>
<div className="col-span-2 space-y-2">
<Label>در چه حوزه ای فعالیت میکنید؟</Label>
<Select>
<SelectTrigger className="w-full bg-background">
<SelectValue placeholder="حوزه را انتخاب کنید" />
</SelectTrigger>
<SelectContent>
<SelectItem value="technology">تکنولوژی</SelectItem>
<SelectItem value="finance">مالی</SelectItem>
<SelectItem value="healthcare">بهداشت و درمان</SelectItem>
<SelectItem value="retail">خرده فروشی</SelectItem>
<SelectItem value="other">سایر</SelectItem>
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-2">
<Label>چند نفر در تیم شما هستن؟</Label>
<Select>
<SelectTrigger className="w-full bg-background">
<SelectValue placeholder="سایز تیم شما" />
</SelectTrigger>
<SelectContent>
<SelectItem value="solo">فقط خودم</SelectItem>
<SelectItem value="small">2-10 نفر</SelectItem>
<SelectItem value="medium">11-50 نفر</SelectItem>
<SelectItem value="large">51-200 نفر</SelectItem>
<SelectItem value="enterprise">200+ نفر</SelectItem>
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-2">
<Label>چه چیزی نقش شما رو به بهترین نحو توصیف میکند؟</Label>
<Select>
<SelectTrigger className="w-full bg-background">
<SelectValue placeholder="نقش را انتخاب کنید" />
</SelectTrigger>
<SelectContent>
<SelectItem value="founder">موسس</SelectItem>
<SelectItem value="sales">فروش</SelectItem>
<SelectItem value="marketing">بازاریابی</SelectItem>
<SelectItem value="operations">عملیات</SelectItem>
<SelectItem value="other">سایر</SelectItem>
</SelectContent>
</Select>
</div>
</form>
);
};
const StepTwoComponent = () => {
const goalOptions = [
{ value: "close-deals", label: "بستن معاملات بیشتر" },
{ value: "grow-network", label: "شبکه ام را رشد بده" },
{ value: "track-pipeline", label: "خط لوله من را دنبال کنید" },
{ value: "automate-outreach", label: "دسترسی خودکار" },
{ value: "team-collaboration", label: "همکاری با تیم" },
{ value: "find-prospects", label: "چشم انداز جدید پیدا کنید" },
{ value: "retain-customers", label: "حفظ مشتریان" },
{ value: "analyze-performance", label: "تجزیه و تحلیل عملکرد" },
{ value: "streamline-workflow", label: "گردش کار را ساده کنید" },
{ value: "other", label: "یک چیز دیگر" },
];
const [selectedGoals, setSelectedGoals] = useState<string[]>([]);
const toggleGoal = (goalValue: string, checked: boolean) => {
setSelectedGoals((prev) =>
checked
? [...prev, goalValue]
: prev.filter((value) => value !== goalValue),
);
};
return (
<form
onSubmit={(e) => e.preventDefault()}
className="grid grid-cols-2 gap-6 pb-2 lg:gap-8"
>
{goalOptions.map((goal) => {
const isSelected = selectedGoals.includes(goal.value);
return (
<Label htmlFor={goal.value} key={goal.value}>
<div
role="button"
className={cn(
"flex w-full cursor-pointer items-center gap-2 rounded-full md:px-6 md:py-4",
selectedGoals.includes(goal.value)
? "md:bg-muted"
: "md:bg-muted/50",
)}
>
<Checkbox
id={goal.value}
checked={isSelected}
onCheckedChange={(checked) =>
toggleGoal(goal.value, checked === true)
}
className="bg-background"
/>
{goal.label}
</div>
</Label>
);
})}
</form>
);
};
const StepThreeComponent = ({
stepIndex,
onValidationChange,
}: StepComponentProps) => {
const [isConnected, setIsConnected] = useState<string | boolean>(false);
const handleConnectButtonClick = (provider: string) => {
setIsConnected(provider);
onValidationChange(stepIndex, true);
};
return (
<div className="flex flex-col items-center justify-center gap-4 md:min-h-[40.5dvh]">
<Button
variant="outline"
onClick={() => handleConnectButtonClick("gmail")}
disabled={!!isConnected}
>
<img
src="/block-media/shadcnblocks/block/logos/google-icon.svg"
className="size-4"
alt="Google"
/>
{isConnected === "gmail" ? "Connected" : "ادامه با گوگل"}
</Button>
<Button
variant="outline"
onClick={() => handleConnectButtonClick("facebook")}
disabled={!!isConnected}
>
<img
src="/block-media/shadcnblocks/block/logos/facebook-icon.svg"
className="size-4"
alt="Facebook"
/>
{isConnected === "facebook" ? "Connected" : "Continue with Facebook"}
</Button>
<Button
variant="link"
onClick={() => handleConnectButtonClick("manual")}
disabled={!!isConnected}
>
{isConnected === "manual" ? "Skipped" : "فعلاً رد شوید"}
</Button>
</div>
);
};
const StepFourComponent = () => {
return (
<div className="flex h-full flex-col items-center justify-between md:min-h-[40.5dvh]">
<img
src="/block-media/shadcnblocks/block/dashboard-placeholder.png"
className="w-full"
style={{
maskImage: "linear-gradient(to bottom, black 0%, transparent 100%)",
}}
alt="Dashboard placeholder"
/>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<Leaf className="size-4 fill-primary stroke-green-200" />
<p>50k+ active users</p>
</div>
<div className="flex items-center gap-2">
<p>امتیاز ۴٫۹ از ۵</p>
<div className="flex items-center gap-1">
{Array.from({ length: 5 }).map((_, i) => {
return <Star key={i} className="size-4 fill-primary" />;
})}
</div>
</div>
</div>
</div>
);
};
const StepFourSecondaryCta = () => {
return (
<Button className="w-full" size="lg">
Get the Extension
</Button>
);
};
const StepFourCta = ({ onClick, disabled }: StepCtaProps) => {
return (
<Button
className="w-full"
size="lg"
variant="ghost"
onClick={onClick}
disabled={disabled}
>
شاید بعداً
</Button>
);
};
const StepFiveComponent = () => {
const premiumFeatures = [
{
icon: LayoutGrid,
title: "Custom Views",
description: "Organize your data exactly how you want it",
},
{
icon: Workflow,
title: "Automation",
description: "Set up triggers and actions to save time",
},
{
icon: Chrome,
title: "Web Clipper",
description: "Save contacts from any website instantly",
},
{
icon: Sparkles,
title: "AI Insights",
description: "Get smart suggestions and auto-complete data",
},
{
icon: Puzzle,
title: "App Connections",
description: "Sync with 2000+ tools you already use",
},
{
icon: Star,
title: "پشتیبانی اولویت‌دار",
description: "Dedicated help and راه‌اندازی اولیه assistance",
},
];
return (
<ul className="space-y-4 md:space-y-6">
{premiumFeatures.map((feature) => (
<li key={feature.title} className="flex items-start gap-2">
<feature.icon className="mt-0.5 size-5 shrink-0" />
<div className="space-y-0.5">
<p className="text-sm leading-tight font-medium md:text-base">
{feature.title}
</p>
<p className="text-xs text-muted-foreground md:text-sm">
{feature.description}
</p>
</div>
</li>
))}
</ul>
);
};
const StepFiveCta = ({ onClick, disabled }: StepCtaProps) => {
return (
<Button className="w-full" size="lg" onClick={onClick} disabled={disabled}>
Activate Trial
</Button>
);
};
interface Onboarding2Props {
steps: StepDef[];
className?: string;
}
const Onboarding2 = ({
steps = [
{
title: " درمورد خودتون به ما بگویید",
description: "چند سوال سریع در مورد تجربه ی شخصیتون",
className: "bg-orange-200",
component: StepOneComponent,
},
{
title: "چی میخوای به دست بیاری؟",
description:
"انتخاب همه که اعمال می‌شوند. این به ما کمک می‌کند داشبورد و پیشنهادهای شما را متناسب‌ سازی کنیم",
className: "bg-blue-200",
component: StepTwoComponent,
},
{
title: "لینک اکانت شما",
description: [
{
icon: User,
title:
"مخاطبین و جلسات و گفتگوهای خود رو به‌ صورت خودکار همگام‌سازی کنید",
},
{
icon: Shield,
title: "دیتا شما ایمن و خصوصی میماند",
},
{
icon: Lock,
title: "رمزنگاری با استاندارد سازمانی",
},
],
className: "bg-pink-200",
isBlocking: true,
component: StepThreeComponent,
},
{
title: "به وب‌گردی خود سرعت و قدرت ببخشید",
description:
"افزونه ما به شما این امکان را می‌دهد که با یک کلیک، از هر وب‌سایتی که هستید مخاطبین را ذخیره کنید و یادداشت بردارید",
className: "bg-green-200",
component: StepFourComponent,
secondaryCta: StepFourSecondaryCta,
cta: StepFourCta,
},
{
title: "تجربه کامل رو باز کنید ( دسترسی به تمام امکانات )",
description:
"دوره آزمایشی ۱۴ روزه خود را آغاز کرده و تمامی ویژگی‌ها را تجربه کنید نیاز به کارت اعتباری نیست.",
className: "bg-purple-200",
component: StepFiveComponent,
cta: StepFiveCta,
},
],
className,
}: Onboarding2Props) => {
const [currentStep, setCurrentStep] = useState(0);
const [stepValidation, setStepValidation] = useState<Record<number, boolean>>(
{},
);
const handleStepValidChange = (stepIndex: number, isValid: boolean) => {
setStepValidation((prev) => ({ ...prev, [stepIndex]: isValid }));
};
const canProceed = () => {
const step = steps[currentStep];
if (!step.component || !step.isBlocking) return true;
return stepValidation[currentStep] === true;
};
const handleNext = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
}
};
const handlePrevious = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const step = steps[currentStep];
return (
<section>
<Background />
<Dialog open={true}>
<DialogContent
showCloseButton={false}
className={cn(
"flex items-center justify-center overflow-hidden rounded-none p-0 md:max-w-3xl md:items-stretch lg:max-w-5xl",
className,
)}
>
<DialogHeader className="sr-only">
<DialogTitle>راه‌اندازی اولیه</DialogTitle>
<DialogDescription>راهنمای شروع کار</DialogDescription>
</DialogHeader>
<div className="flex min-h-[40dvh] w-full flex-col-reverse items-start tracking-tighter md:flex-row">
<div className="flex h-full w-full flex-col justify-between gap-4 p-6 pb-6 md:flex-[45%] md:gap-6 lg:p-12">
<div className="space-y-2">
<FadeContainer condition={currentStep > 0}>
<Button
variant="ghost"
size="sm"
onClick={handlePrevious}
disabled={currentStep === 0}
>
<ChevronLeftIcon />
بازگشت
</Button>
</FadeContainer>
<StepDescriptionCard
title={step.title}
description={step.description}
/>
</div>
<div className="space-y-4 md:space-y-6">
<FadeContainer condition={canProceed()} className="space-y-2">
{step.cta ? (
<step.cta onClick={handleNext} disabled={!canProceed()} />
) : (
<Button
size="lg"
className="w-full"
onClick={handleNext}
disabled={
currentStep === steps.length - 1 || !canProceed()
}
>
ادامه
</Button>
)}
{step.secondaryCta && <step.secondaryCta />}
</FadeContainer>
<StepIndicator
totalSteps={steps.length}
currentStep={currentStep}
/>
</div>
</div>
<div
className={cn(
"flex h-full w-full flex-[55%] shrink-0 items-center justify-center p-6 transition-colors lg:p-12",
step.className || "bg-foreground/30",
)}
>
<step.component
stepIndex={currentStep}
onValidationChange={handleStepValidChange}
/>
</div>
</div>
</DialogContent>
</Dialog>
</section>
);
};
export { Onboarding2 };