Files
reui-blocks/blocks/onboarding2.tsx
T

652 lines
19 KiB
TypeScript
Raw Normal View History

"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 };