Initial commit: dating app web frontend (Next.js)

Phone+OTP auth, staged onboarding, admin-approval-gated home screen,
and the full discovery/match/chat UI (swipe deck with drag gestures,
match modal, profile detail, filters, messages/chat) wired to the
real backend, styled after a Figma dating-app UI kit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 23:24:34 +03:30
commit 93f6ea2f07
63 changed files with 11802 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { motion } from "framer-motion";
import { ShieldCheck, Sparkles, Heart } from "lucide-react";
import { Button } from "@/components/ui/Button";
import { cn } from "@/lib/utils";
interface Slide {
icon: React.ReactNode;
gradient: string;
title: string;
description: string;
}
const SLIDES: Slide[] = [
{
icon: <ShieldCheck size={64} className="text-white" strokeWidth={1.75} />,
gradient: "linear-gradient(160deg, #ff8ca3 0%, #ff4d6d 55%, #d81b47 100%)",
title: "پروفایل‌های تایید شده",
description:
"قبل از این‌که کسی وارد بخش دوستیابی بشه، تیم دلدل پروفایلش رو بررسی می‌کنه؛ یعنی خبری از ربات و پروفایل‌های تقلبی نیست.",
},
{
icon: <Sparkles size={64} className="text-white" strokeWidth={1.75} />,
gradient: "linear-gradient(160deg, #ffc93c 0%, #ff8ca3 55%, #ff4d6d 100%)",
title: "مچ‌های واقعی",
description:
"بر اساس علاقه‌مندی‌ها، سبک زندگی و چیزهایی که برات مهمه، با آدم‌های مناسب‌تر آشنا می‌شی.",
},
{
icon: <Heart size={64} className="text-white" fill="white" strokeWidth={0} />,
gradient: "linear-gradient(160deg, #8b5cf6 0%, #ff4d6d 55%, #ff8ca3 100%)",
title: "دوستیابی، ساده و امن",
description:
"یکی‌یکی پروفایل‌ها رو ببین، اگه خوشت اومد لایک کن، نه که نه؛ همه‌چیز دست خودته.",
},
];
export default function WelcomePage() {
const router = useRouter();
const scrollerRef = useRef<HTMLDivElement>(null);
const [active, setActive] = useState(0);
useEffect(() => {
const el = scrollerRef.current;
if (!el) return;
function onScroll() {
if (!el) return;
const index = Math.round(el.scrollLeft / el.clientWidth);
setActive((prev) => (prev === index ? prev : index));
}
el.addEventListener("scroll", onScroll, { passive: true });
return () => el.removeEventListener("scroll", onScroll);
}, []);
function goToSlide(index: number) {
const el = scrollerRef.current;
if (!el) return;
el.scrollTo({ left: index * el.clientWidth, behavior: "smooth" });
}
return (
<div className="flex flex-col min-h-dvh bg-(--color-bg-primary)">
<div
ref={scrollerRef}
dir="ltr"
className="flex-1 flex overflow-x-auto snap-x snap-mandatory scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{SLIDES.map((slide, i) => (
<div
key={i}
dir="rtl"
className="w-full shrink-0 snap-center flex flex-col items-center px-8 pt-[calc(env(safe-area-inset-top)+32px)]"
>
<motion.div
initial={{ opacity: 0, scale: 0.85, y: 12 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="w-full aspect-square max-w-[280px] rounded-(--radius-xl) flex items-center justify-center shadow-(--shadow-cute-lg) mt-6"
style={{ background: slide.gradient }}
>
{slide.icon}
</motion.div>
<div className="flex flex-col items-center text-center gap-3 mt-10 max-w-xs">
<h1 className="font-[var(--font-display)] font-extrabold text-[26px] text-(--color-content-primary)">
{slide.title}
</h1>
<p className="text-[14px] text-(--color-content-secondary) leading-7">
{slide.description}
</p>
</div>
</div>
))}
</div>
<div className="flex flex-col gap-6 px-8 pb-[calc(env(safe-area-inset-bottom)+28px)] pt-4">
<div className="flex items-center justify-center gap-2">
{SLIDES.map((_, i) => (
<button
key={i}
aria-label={`اسلاید ${i + 1}`}
onClick={() => goToSlide(i)}
className={cn(
"h-2 rounded-(--radius-full) transition-all duration-300",
i === active
? "w-6 bg-(--color-brand-500)"
: "w-2 bg-(--color-bg-tertiary)"
)}
/>
))}
</div>
<Button size="lg" isBlock onClick={() => router.push("/login")}>
بساز حساب کاربری
</Button>
<button
onClick={() => router.push("/login")}
className="text-[13px] text-center text-(--color-content-secondary)"
>
قبلاً حساب داری؟ <span className="font-bold text-(--color-brand-600)">ورود</span>
</button>
</div>
</div>
);
}