242 lines
7.8 KiB
TypeScript
242 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
import { Calendar } from "lucide-react";
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface Waitlist2Props {
|
|
logo?: string;
|
|
badge?: string;
|
|
heading?: string;
|
|
description?: string;
|
|
avatars?: string[];
|
|
joinedPeople?: number;
|
|
launchDate?: string;
|
|
className?: string;
|
|
}
|
|
|
|
const COUNTDOWN_UNITS = [
|
|
{ key: "day", label: "روز" },
|
|
{ key: "hour", label: "ساعت" },
|
|
{ key: "minute", label: "دقیقه" },
|
|
{ key: "second", label: "ثانیه" },
|
|
] as const;
|
|
|
|
type CountdownTimeLeft = Record<
|
|
(typeof COUNTDOWN_UNITS)[number]["key"],
|
|
number
|
|
>;
|
|
|
|
const formatCountdownValue = (value: number) =>
|
|
value.toLocaleString("fa-IR", { minimumIntegerDigits: 2 });
|
|
|
|
const SignupForm = () => {
|
|
return (
|
|
<div className="relative mt-8 w-full max-w-md overflow-hidden rounded-xl p-px md:rounded-lg">
|
|
<div
|
|
style={{
|
|
background:
|
|
"conic-gradient(transparent 70%, var(--color-blue-500) 90%)",
|
|
animationDuration: "4s",
|
|
}}
|
|
className="absolute top-1/2 aspect-square w-full -translate-y-1/2 scale-200 animate-spin opacity-70"
|
|
/>
|
|
<form className="relative z-10 flex w-full flex-col items-center gap-2 rounded-xl bg-background p-2 md:flex-row md:rounded-lg md:p-1">
|
|
<Input
|
|
className="w-full"
|
|
required
|
|
type="email"
|
|
placeholder="ایمیل خود را وارد کنید"
|
|
/>
|
|
<Button type="submit" className="w-full shrink-0 md:w-auto">
|
|
پیوستن به لیست انتظار
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface JoinedPeopleProps {
|
|
joinedPeople: number;
|
|
avatars: string[];
|
|
}
|
|
|
|
const JoinedPeople = ({ joinedPeople, avatars }: JoinedPeopleProps) => {
|
|
return (
|
|
<div className="mt-4 flex items-center justify-center">
|
|
<div className="relative flex flex-col items-center gap-2 md:flex-row md:gap-4">
|
|
<div className="flex items-center">
|
|
{avatars.map((avatar, index) => {
|
|
return (
|
|
<div key={`waitlist-avatar-${index}`} className="h-8 w-6">
|
|
<div className="size-8">
|
|
<img
|
|
src={avatar}
|
|
alt={`عضو ${(index + 1).toLocaleString("fa-IR")}`}
|
|
className="size-full shrink-0 rounded-full bg-background object-cover object-top p-0.5"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
بیش از {joinedPeople.toLocaleString("fa-IR")} نفر قبلاً پیوستهاند
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface CountdownTimerProps {
|
|
targetDate: string;
|
|
}
|
|
|
|
const CountdownTimer = ({ targetDate }: CountdownTimerProps) => {
|
|
const [timeLeft, setTimeLeft] = useState<CountdownTimeLeft>({
|
|
day: 0,
|
|
hour: 0,
|
|
minute: 0,
|
|
second: 0,
|
|
});
|
|
|
|
useEffect(() => {
|
|
const calculateTimeLeft = () => {
|
|
const target = new Date(targetDate).getTime();
|
|
const now = new Date().getTime();
|
|
const difference = target - now;
|
|
|
|
if (difference > 0) {
|
|
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
|
|
const hours = Math.floor(
|
|
(difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
|
|
);
|
|
const minutes = Math.floor(
|
|
(difference % (1000 * 60 * 60)) / (1000 * 60),
|
|
);
|
|
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
|
|
|
|
setTimeLeft({
|
|
day: days,
|
|
hour: hours,
|
|
minute: minutes,
|
|
second: seconds,
|
|
});
|
|
} else {
|
|
setTimeLeft({ day: 0, hour: 0, minute: 0, second: 0 });
|
|
}
|
|
};
|
|
|
|
calculateTimeLeft();
|
|
|
|
const timer = setInterval(calculateTimeLeft, 1000);
|
|
|
|
return () => clearInterval(timer);
|
|
}, [targetDate]);
|
|
|
|
return (
|
|
<div className="mt-6 flex flex-col items-center gap-6">
|
|
<div className="flex justify-center gap-6">
|
|
{COUNTDOWN_UNITS.map(({ key, label }, index) => {
|
|
const value = timeLeft[key];
|
|
|
|
return (
|
|
<React.Fragment key={`waitlist-timer-${key}`}>
|
|
<div className="flex flex-col items-center gap-2">
|
|
<span className="text-xl">{formatCountdownValue(value)}</span>
|
|
<span className="text-xs text-muted-foreground">{label}</span>
|
|
</div>
|
|
{index < COUNTDOWN_UNITS.length - 1 ? (
|
|
<span className="text-muted-foreground">:</span>
|
|
) : null}
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="text-muted-foreground" size={14} />
|
|
<span className="text-xs leading-0">
|
|
زمان باقیمانده تا راهاندازی
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const Waitlist2 = ({
|
|
logo = "/block-media/shadcnblocks/block/block-white-1.svg",
|
|
badge = `بهزودی ${(new Date().getFullYear() + 1).toLocaleString("fa-IR")}`,
|
|
heading = "به لیست انتظار بپیوندید",
|
|
description = "زودتر از همه از راهاندازی مطلع شوید. با ثبت ایمیل، دسترسی زودهنگام و مزایای ویژه دریافت کنید.",
|
|
avatars = [
|
|
"/block-media/shadcnblocks/block/portraits/joseph-gonzalez-iFgRcqHznqg-unsplash.jpg",
|
|
"/block-media/shadcnblocks/block/portraits/michael-dam-mEZ3PoFGs_k-unsplash.jpg",
|
|
"/block-media/shadcnblocks/block/portraits/nima-motaghian-nejad-_omdf_EgRUo-unsplash.jpg",
|
|
"/block-media/shadcnblocks/block/portraits/good-faces-xmSWVeGEnJw-unsplash.jpg",
|
|
"/block-media/shadcnblocks/block/portraits/christian-buehner-DItYlc26zVI-unsplash 1.png",
|
|
],
|
|
joinedPeople = 12500,
|
|
launchDate = `01/01/${new Date().getFullYear() + 1}`,
|
|
className,
|
|
}: Waitlist2Props) => {
|
|
return (
|
|
<section dir="rtl" lang="fa" className={cn("py-32", className)}>
|
|
<div className="container">
|
|
<div className="flex flex-col items-center">
|
|
<div className="flex flex-col items-center gap-6">
|
|
<div className="flex size-12 items-center justify-center rounded-2xl bg-primary p-2">
|
|
<img src={logo} alt="لوگو" className="size-full" />
|
|
</div>
|
|
<Badge variant="outline">
|
|
<div className="relative flex items-center justify-center">
|
|
<span className="size-1.5 rounded-full bg-primary" />
|
|
<motion.span
|
|
initial={{
|
|
scale: 1,
|
|
opacity: 1,
|
|
}}
|
|
animate={{
|
|
scale: 2,
|
|
opacity: 0,
|
|
}}
|
|
transition={{
|
|
duration: 1,
|
|
ease: "easeInOut",
|
|
repeat: Infinity,
|
|
repeatType: "loop",
|
|
repeatDelay: 0.5,
|
|
}}
|
|
className="absolute size-1.5 rounded-full bg-primary"
|
|
/>
|
|
</div>
|
|
{badge}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="mt-6 flex flex-col items-center gap-2">
|
|
<h3 className="text-center text-3xl font-medium lg:text-xl">
|
|
{heading}
|
|
</h3>
|
|
<p className="max-w-md text-center text-muted-foreground">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
|
|
<SignupForm />
|
|
|
|
<JoinedPeople joinedPeople={joinedPeople} avatars={avatars} />
|
|
|
|
<CountdownTimer targetDate={launchDate} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Waitlist2 };
|