- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
310 lines
18 KiB
TypeScript
310 lines
18 KiB
TypeScript
"use client";
|
||
import React, { useState } from "react";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import {
|
||
Dog,
|
||
Cat,
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
ShieldCheck,
|
||
Activity,
|
||
Bone,
|
||
Sparkles,
|
||
Pill,
|
||
Sparkle
|
||
} from "lucide-react";
|
||
import { toPersian, cn } from "../lib/utils";
|
||
|
||
import { useRouter } from "next/navigation";
|
||
import { useUserStore } from "../lib/store/userStore";
|
||
import { useUIStore } from "../lib/store/uiStore";
|
||
import { usePetStore } from "../lib/store/usePetStore";
|
||
|
||
interface SmartAdvisorProps {
|
||
onComplete?: (data: any) => void;
|
||
}
|
||
|
||
const MEDICAL_OPTIONS = [
|
||
{ id: "joint_surgery", label: "جراحی مفاصل", condition: "جراحی مفاصل" },
|
||
{ id: "recent_birth", label: "زایمان اخیر", condition: "زایمان اخیر" },
|
||
{ id: "pregnancy", label: "بارداری", condition: "بارداری" },
|
||
{ id: "digestion", label: "مشکلات گوارشی", condition: "مشکلات گوارشی" },
|
||
{ id: "hair_loss", label: "ریزش موی شدید", condition: "ریزش موی شدید" },
|
||
{ id: "appetite", label: "بیاشتهایی", condition: "بیاشتهایی" },
|
||
];
|
||
|
||
export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorProps & { rules?: any[] }) {
|
||
const router = useRouter();
|
||
const { isLoggedIn } = useUserStore();
|
||
const setLoginModalOpen = useUIStore(state => state.setLoginModalOpen);
|
||
const { addPet, setActivePet } = usePetStore();
|
||
const texts = useSettingsStore(state => state.texts);
|
||
const medicalOptions = React.useMemo(() => {
|
||
const jsonStr = texts['medical_options'];
|
||
if (jsonStr) {
|
||
try {
|
||
return JSON.parse(jsonStr) as typeof MEDICAL_OPTIONS;
|
||
} catch (e) {
|
||
console.error("Failed to parse medical_options JSON:", e);
|
||
}
|
||
}
|
||
return MEDICAL_OPTIONS;
|
||
}, [texts]);
|
||
|
||
const [step, setStep] = useState(1);
|
||
|
||
// Step 1 Data
|
||
const [name, setName] = useState("");
|
||
const [type, setType] = useState<"سگ" | "گربه">("سگ");
|
||
const [breed, setBreed] = useState("");
|
||
|
||
// Step 2 Data
|
||
const [age, setAge] = useState<number>(3);
|
||
const [weight, setWeight] = useState<number>(10);
|
||
const [activityLevel, setActivityLevel] = useState<"کم" | "متوسط" | "زیاد">("متوسط");
|
||
|
||
// Step 3 Data
|
||
const [medicalConditions, setMedicalConditions] = useState<string[]>([]);
|
||
|
||
const handleNext = () => setStep(s => s + 1);
|
||
const handleBack = () => setStep(s => s - 1);
|
||
|
||
const handleSubmit = () => {
|
||
const data = {
|
||
name, type, breed, age, weight, activityLevel, medicalConditions
|
||
};
|
||
if (onComplete) {
|
||
onComplete(data);
|
||
} else {
|
||
if (!isLoggedIn) {
|
||
setLoginModalOpen(true, data);
|
||
} else {
|
||
const newPet: any = { ...data, id: Date.now().toString(), reminders: [], logs: [], consumptions: [] };
|
||
addPet(newPet);
|
||
setActivePet(newPet.id);
|
||
router.push('/profile');
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<section id="canino-advisor" className="py-24 bg-white">
|
||
<div className="max-w-4xl mx-auto px-4">
|
||
<div className="text-center mb-12">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 rounded-full mb-6"
|
||
>
|
||
<Sparkle className="w-4 h-4 text-canina-blue" />
|
||
<span className="text-canina-blue text-xs font-black uppercase tracking-widest font-vazir">تکنولوژی پایش هوشمند</span>
|
||
</motion.div>
|
||
<h2 className="text-4xl lg:text-5xl font-black text-medical-gray-900 mb-4 font-vazir italic">دستیار سلامت کانینا</h2>
|
||
<p className="text-medical-gray-600 font-bold text-lg font-vazir">فقط چند قدم تا صدور شناسنامه هوشمند و دریافت پیشنهادات تخصصی</p>
|
||
</div>
|
||
|
||
<div className="bg-medical-gray-50 rounded-[3.5rem] border border-medical-gray-100 p-8 lg:p-12 shadow-2xl shadow-medical-gray-200/50 relative overflow-hidden">
|
||
{/* Progress Indicator */}
|
||
<div className="absolute top-0 left-0 w-full h-2 bg-medical-gray-200">
|
||
<motion.div
|
||
className="h-full bg-canina-blue"
|
||
initial={{ width: "0%" }}
|
||
animate={{ width: `${(step / 3) * 100}%` }}
|
||
transition={{ duration: 0.5 }}
|
||
/>
|
||
</div>
|
||
|
||
<AnimatePresence mode="wait">
|
||
{step === 1 && (
|
||
<motion.div
|
||
key="step1"
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: -20 }}
|
||
className="space-y-8"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام اول: هویت بصری</h3>
|
||
<p className="text-medical-gray-600 font-bold font-vazir">همدم شما رو با چه اسمی صدا میزنید؟</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<button
|
||
onClick={() => setType("سگ")}
|
||
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none ${type === "سگ" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105' : 'border-transparent bg-white/50 text-medical-gray-400 hover:bg-white'}`}
|
||
>
|
||
<Dog className={`w-12 h-12 ${type === "سگ" ? 'text-canina-blue' : 'text-current'}`} />
|
||
<span className="text-lg font-black font-vazir">سگ</span>
|
||
</button>
|
||
<button
|
||
onClick={() => setType("گربه")}
|
||
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none ${type === "گربه" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105' : 'border-transparent bg-white/50 text-medical-gray-400 hover:bg-white'}`}
|
||
>
|
||
<Cat className={`w-12 h-12 ${type === "گربه" ? 'text-canina-blue' : 'text-current'}`} />
|
||
<span className="text-lg font-black font-vazir">گربه</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid md:grid-cols-2 gap-6">
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-black text-medical-gray-600 pr-2">نام پت</label>
|
||
<input
|
||
type="text"
|
||
value={name}
|
||
onChange={e => setName(e.target.value)}
|
||
placeholder="لوسی، تدی..."
|
||
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-black text-lg font-vazir placeholder-medical-gray-500 text-medical-gray-900"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-black text-medical-gray-600 pr-2">نژاد</label>
|
||
<input
|
||
type="text"
|
||
value={breed}
|
||
onChange={e => setBreed(e.target.value)}
|
||
placeholder="ژرمن، پرشین..."
|
||
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-bold font-vazir placeholder-medical-gray-500 text-medical-gray-900"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
onClick={handleNext}
|
||
disabled={!name || !breed}
|
||
className="w-full py-6 rounded-3xl font-black text-lg flex items-center justify-center gap-3 font-vazir min-h-[48px] transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:shadow-none disabled:border-2 disabled:border-gray-400 bg-medical-gray-900 text-white hover:bg-canina-blue shadow-xl shadow-medical-gray-900/10"
|
||
>
|
||
ثبت هویت و ادامه
|
||
<ChevronLeft className="w-5 h-5" />
|
||
</button>
|
||
{(!name || !breed) && (
|
||
<p className="text-center text-xs font-black text-amber-600 mt-2 font-vazir animate-pulse">
|
||
⚠️ لطفاً ابتدا نام و نژاد پت را در بالا وارد کنید.
|
||
</p>
|
||
)}
|
||
</motion.div>
|
||
)}
|
||
|
||
{step === 2 && (
|
||
<motion.div
|
||
key="step2"
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: -20 }}
|
||
className="space-y-8"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام دوم: پایش فیزیکی</h3>
|
||
<p className="text-medical-gray-600 font-bold font-vazir">اطلاعات فیزیکی دقیق به دوزبندی صحیح مکملها کمک میکند</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div className="space-y-4">
|
||
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">سن حیوان (سال)</label>
|
||
<div className="flex items-center gap-4 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
|
||
<button onClick={() => setAge(Math.max(0, age - 1))} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">-</button>
|
||
<span className="flex-1 text-center text-2xl font-black text-canina-blue font-vazir">{toPersian(age.toString())} <span className="text-sm font-bold text-medical-gray-400">سال</span></span>
|
||
<button onClick={() => setAge(age + 1)} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">+</button>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-4">
|
||
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">وزن حیوان (کیلوگرم)</label>
|
||
<div className="flex items-center gap-4 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
|
||
<button onClick={() => setWeight(Math.max(1, weight - 1))} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">-</button>
|
||
<span className="flex-1 text-center text-2xl font-black text-canina-blue font-vazir">{toPersian(weight.toString())} <span className="text-sm font-bold text-medical-gray-400">kg</span></span>
|
||
<button onClick={() => setWeight(weight + 1)} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">+</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<label className="block text-center text-xs font-black text-medical-gray-600 uppercase tracking-widest font-vazir">سطح فعالیت روزانه</label>
|
||
<div className="grid grid-cols-3 gap-3">
|
||
{["کم", "متوسط", "زیاد"].map(level => (
|
||
<button
|
||
key={level}
|
||
onClick={() => setActivityLevel(level as any)}
|
||
className={`py-4 rounded-2xl border-2 transition-all font-black italic focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${activityLevel === level ? 'border-canina-blue bg-white text-canina-blue shadow-lg' : 'border-transparent bg-white/50 text-medical-gray-500 hover:bg-white'}`}
|
||
>
|
||
{level}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-4 pt-4">
|
||
<button
|
||
onClick={handleBack}
|
||
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
|
||
>
|
||
<ChevronRight className="w-5 h-5" />
|
||
قبلی
|
||
</button>
|
||
<button
|
||
onClick={handleNext}
|
||
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
|
||
>
|
||
گام نهایی سلامت
|
||
<ChevronLeft className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{step === 3 && (
|
||
<motion.div
|
||
key="step3"
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: -20 }}
|
||
className="space-y-8"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام آخر: سوابق و حساسیتها</h3>
|
||
<p className="text-medical-gray-600 font-bold font-vazir">در صورت وجود هر یک از موارد زیر، آن را تیک بزنید</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
{medicalOptions.map((opt) => (
|
||
<button
|
||
key={opt.id}
|
||
onClick={() => {
|
||
setMedicalConditions(prev =>
|
||
prev.includes(opt.condition) ? prev.filter(c => c !== opt.condition) : [...prev, opt.condition]
|
||
);
|
||
}}
|
||
className={`flex items-center gap-4 p-5 rounded-2xl border-2 transition-all text-right focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${medicalConditions.includes(opt.condition) ? 'border-canina-blue bg-white shadow-md' : 'border-transparent bg-white/50 text-medical-gray-600 hover:bg-white'}`}
|
||
>
|
||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center border ${medicalConditions.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 text-transparent'}`}>
|
||
<ShieldCheck className="w-5 h-5" />
|
||
</div>
|
||
<span className={`text-sm font-black font-vazir ${medicalConditions.includes(opt.condition) ? 'text-canina-blue' : 'text-medical-gray-900'}`}>{opt.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="flex gap-4 pt-4">
|
||
<button
|
||
onClick={handleBack}
|
||
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
|
||
>
|
||
<ChevronRight className="w-5 h-5" />
|
||
قبلی
|
||
</button>
|
||
<button
|
||
onClick={handleSubmit}
|
||
className="flex-[2] py-6 bg-canina-blue text-white rounded-3xl font-black text-lg hover:bg-indigo-700 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-canina-blue/20 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
|
||
>
|
||
تکمیل و صدور شناسنامه
|
||
<ChevronLeft className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|