From b3b604af880d0b52d07a9a050ad2b2acb7d6017f Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 4 Mar 2026 09:28:46 +0000 Subject: [PATCH] Edit app-9xzmfic2e4g1/src/pages/PlannerPage.tsx via Editor --- app-9xzmfic2e4g1/src/pages/PlannerPage.tsx | 611 ++++++--------------- 1 file changed, 158 insertions(+), 453 deletions(-) diff --git a/app-9xzmfic2e4g1/src/pages/PlannerPage.tsx b/app-9xzmfic2e4g1/src/pages/PlannerPage.tsx index e133660..d3de30a 100644 --- a/app-9xzmfic2e4g1/src/pages/PlannerPage.tsx +++ b/app-9xzmfic2e4g1/src/pages/PlannerPage.tsx @@ -1,459 +1,164 @@ -import { useState, useMemo, useEffect, useRef, useCallback, memo } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useAuth } from '@/contexts/AuthContext'; -import api from '@/db/api'; -import { Label } from '@/components/ui/label'; -import { Progress } from '@/components/ui/progress'; -import { Form, FormField, FormItem, FormMessage } from '@/components/ui/form'; -import { toast } from 'sonner'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import * as z from 'zod'; -import { format, differenceInDays } from 'date-fns'; -import { Loader2, ArrowRight, ArrowLeft, Sparkles, MapPin, Calendar, Users, Coffee, Heart } from 'lucide-react'; -import { parseApiError } from '@/utils/errorHandler'; -import { retryWithBackoff, withTimeout } from '@/utils/retryWithBackoff'; -import { motion, AnimatePresence } from 'framer-motion'; -import { Button } from '@/components/ui/button'; +import { + Hotel, Home, Navigation, Plane, Trees, Building2, Camera, + Bike, UtensilsCrossed, Users, Heart, Baby, + Car, Bus, Shuffle, Gem, Wallet, CreditCard, Crown, + PersonStanding +} from 'lucide-react'; -// Constants -import { LOADING_STEPS } from '@/constants/planner'; +export const ACCOMMODATION_OPTIONS = [ + { + id: 'hotel', + label: 'Otel', + description: 'Konforlu & servis odaklı', + icon: Hotel, + }, + { + id: 'airbnb', + label: 'Airbnb / Ev', + description: 'Yerel & özgün deneyim', + icon: Home, + }, + { + id: 'center', + label: 'Merkezi', + description: 'Her şeye yakın konum', + icon: Navigation, + }, +] as const; -// Components -import { DateSelector } from '@/components/planner/DateSelector'; -import { TravelerInput } from '@/components/planner/TravelerInput'; -import { AccommodationSelector } from '@/components/planner/AccommodationSelector'; -import { InterestsGrid } from '@/components/planner/InterestsGrid'; +export const TRAVEL_TYPE_OPTIONS = [ + { + id: 'solo', + label: 'Solo', + description: 'Kendi tempomda özgür', + icon: PersonStanding, + gradient: 'from-violet-500 to-purple-600', + bg: 'bg-violet-50', + border: 'border-violet-200', + text: 'text-violet-600', + }, + { + id: 'couple', + label: 'Çift', + description: 'Romantik & özel anlar', + icon: Heart, + gradient: 'from-rose-500 to-pink-600', + bg: 'bg-rose-50', + border: 'border-rose-200', + text: 'text-rose-600', + }, + { + id: 'family', + label: 'Aile', + description: 'Çocuklar dahil herkes', + icon: Baby, + gradient: 'from-amber-500 to-orange-500', + bg: 'bg-amber-50', + border: 'border-amber-200', + text: 'text-amber-600', + }, + { + id: 'friends', + label: 'Arkadaşlar', + description: 'Grup enerjisi & macera', + icon: Users, + gradient: 'from-emerald-500 to-teal-600', + bg: 'bg-emerald-50', + border: 'border-emerald-200', + text: 'text-emerald-600', + }, +] as const; -// Form validation schema -const formSchema = z.object({ - dateRange: z.object({ - from: z.date({ - required_error: 'Başlangıç tarihi gereklidir', - }), - to: z.date({ - required_error: 'Bitiş tarihi gereklidir', - }), - }).refine( - (data) => { - if (!data.from || !data.to) return false; - const today = new Date(); - today.setHours(0, 0, 0, 0); - return data.from >= today; - }, - { - message: 'Başlangıç tarihi bugünden önce olamaz', - } - ).refine( - (data) => { - if (!data.from || !data.to) return false; - return data.to > data.from; - }, - { - message: 'Bitiş tarihi başlangıç tarihinden sonra olmalıdır', - } - ).refine( - (data) => { - if (!data.from || !data.to) return false; - const days = differenceInDays(data.to, data.from) + 1; - return days >= 1 && days <= 14; - }, - { - message: 'Seyahat süresi 1-14 gün arasında olmalıdır', - } - ), - travelers: z.number() - .min(1, 'En az 1 yolcu olmalıdır') - .max(15, '15+ kişi için lütfen bizimle iletişime geçin'), - accommodation: z.string(), - interests: z.array(z.string()) - .min(1, 'En az 1 ilgi alanı seçmelisiniz') - .max(6, 'Maksimum 6 ilgi alanı seçebilirsiniz'), -}); +export const TRANSPORT_OPTIONS = [ + { + id: 'rental', + label: 'Araç Kiralama', + description: 'Özgür & esnek rota', + icon: Car, + }, + { + id: 'transfer', + label: 'Özel Transfer', + description: 'Konforlu & zahmetsiz', + icon: Gem, + }, + { + id: 'shuttle', + label: 'Servis / Minibüs', + description: 'Ekonomik grup ulaşımı', + icon: Bus, + }, + { + id: 'mixed', + label: 'Karma', + description: 'Duruma göre en iyisi', + icon: Shuffle, + }, +] as const; -type FormValues = z.infer; +export const BUDGET_OPTIONS = [ + { + id: 'budget', + label: 'Ekonomik', + description: 'Akıllı harcama, tam deneyim', + range: '₺500 – ₺1.000 / gün', + icon: Wallet, + tier: 1, + color: 'text-sky-600', + activeBg: 'bg-sky-50', + activeBorder: 'border-sky-400', + dot: 'bg-sky-400', + }, + { + id: 'moderate', + label: 'Orta', + description: 'Kalite & tasarruf dengesi', + range: '₺1.000 – ₺2.500 / gün', + icon: CreditCard, + tier: 2, + color: 'text-emerald-600', + activeBg: 'bg-emerald-50', + activeBorder: 'border-emerald-400', + dot: 'bg-emerald-400', + }, + { + id: 'comfort', + label: 'Konforlu', + description: 'Ekstra konfor & özel deneyim', + range: '₺2.500 – ₺5.000 / gün', + icon: Gem, + tier: 3, + color: 'text-orange-600', + activeBg: 'bg-orange-50', + activeBorder: 'border-orange-400', + dot: 'bg-orange-400', + }, + { + id: 'luxury', + label: 'Lüks', + description: 'Sınırsız & ayrıcalıklı', + range: '₺5.000+ / gün', + icon: Crown, + tier: 4, + color: 'text-amber-600', + activeBg: 'bg-amber-50', + activeBorder: 'border-amber-400', + dot: 'bg-amber-400', + }, +] as const; -const STEPS = [ - { id: 'dates', title: 'Tarihler', icon: Calendar, description: 'Ne zaman gidiyorsunuz?' }, - { id: 'travelers', title: 'Seyahat Grubu', icon: Users, description: 'Kiminle seyahat ediyorsunuz?' }, - { id: 'accommodation', title: 'Konaklama', icon: Coffee, description: 'Nasıl bir konaklama istersiniz?' }, - { id: 'interests', title: 'İlgi Alanları', icon: Heart, description: 'Neleri keşfetmek istersiniz?' } -]; +export const INTEREST_OPTIONS = [ + { id: 'balloon', label: 'Sıcak Hava Balonu', icon: Plane }, + { id: 'nature', label: 'Doğa & Yürüyüş', icon: Trees }, + { id: 'history', label: 'Tarih & Kültür', icon: Building2 }, + { id: 'photography', label: 'Fotoğraf', icon: Camera }, + { id: 'adventure', label: 'Macera', icon: Bike }, + { id: 'gastronomy', label: 'Gastronomi', icon: UtensilsCrossed }, +] as const; -const PlannerPage = () => { - const { user } = useAuth(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - const [loadingStep, setLoadingStep] = useState(0); - const [currentStep, setCurrentStep] = useState(0); - const [datePickerOpen, setDatePickerOpen] = useState(false); - const abortControllerRef = useRef(null); - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - dateRange: { - from: undefined, - to: undefined, - }, - travelers: 2, - accommodation: 'center', - interests: [], - }, - }); - - const watchedValues = form.watch(); - - const nextStep = async () => { - // Validate current step fields - const currentStepId = STEPS[currentStep].id; - let isValid = false; - - if (currentStepId === 'dates') { - isValid = await form.trigger('dateRange'); - } else if (currentStepId === 'travelers') { - isValid = await form.trigger('travelers'); - } else if (currentStepId === 'accommodation') { - isValid = await form.trigger('accommodation'); - } else if (currentStepId === 'interests') { - isValid = await form.trigger('interests'); - } - - if (isValid && currentStep < STEPS.length - 1) { - setCurrentStep(prev => prev + 1); - } - }; - - const prevStep = () => { - if (currentStep > 0) { - setCurrentStep(prev => prev - 1); - } - }; - - const handleInterestToggle = useCallback((interestId: string) => { - const currentInterests = form.getValues('interests'); - const newInterests = currentInterests.includes(interestId) - ? currentInterests.filter(id => id !== interestId) - : [...currentInterests, interestId]; - form.setValue('interests', newInterests, { shouldValidate: true }); - }, [form]); - - const simulateLoadingSteps = useCallback(() => { - setLoadingStep(0); - const interval = setInterval(() => { - setLoadingStep(prev => { - if (prev < LOADING_STEPS.length - 1) return prev + 1; - clearInterval(interval); - return prev; - }); - }, 2500); - return interval; - }, []); - - const onSubmit = async (data: FormValues) => { - setLoading(true); - setLoadingStep(0); - abortControllerRef.current = new AbortController(); - - const loadingInterval = simulateLoadingSteps(); - - try { - const startDate = format(data.dateRange.from, 'yyyy-MM-dd'); - const endDate = format(data.dateRange.to, 'yyyy-MM-dd'); - - const formData = { - startDate, - endDate, - interests: data.interests, - dailySchedule: 'moderate', - preferences: `Accommodation: ${data.accommodation}, Travelers: ${data.travelers}`, - }; - - const result = await retryWithBackoff( - async () => { - return await withTimeout( - api.generateItinerary(formData), - 45000, - new Error('Sunucu yanıt vermiyor, lütfen tekrar deneyin.') - ); - }, - { - maxRetries: 2, - initialDelay: 1000, - maxDelay: 5000 - } - ); - - clearInterval(loadingInterval); - - let tripId = ''; - if (user) { - const savedTrip = await api.saveTrip({ - user_id: user.id, - title: `Kapadokya Gezisi`, - destination: 'Cappadocia', - start_date: startDate, - end_date: endDate, - preferences: formData, - itinerary: result, - }); - tripId = savedTrip.id; - } else { - sessionStorage.setItem('pending_trip', JSON.stringify(result)); - navigate('/login', { state: { from: '/planner', message: 'Planınızı kaydetmek için giriş yapın' } }); - return; - } - - navigate(`/trip/${tripId}`); - toast.success('Rotanız hazır!'); - } catch (err) { - clearInterval(loadingInterval); - if (err instanceof Error && err.name === 'AbortError') return; - const apiError = parseApiError(err); - toast.error('Hata oluştu', { description: apiError.userMessage }); - } finally { - setLoading(false); - setLoadingStep(0); - } - }; - - const progress = ((currentStep + 1) / STEPS.length) * 100; - - if (loading) { - return ( -
-
- Loading bg -
- -
- - - - -
-

- {LOADING_STEPS[loadingStep].label} -

-
- -
-
- -

- "Sizin için en kusursuz Kapadokya efsanesini kurguluyoruz..." -

-
-
- ); - } - - return ( -
- {/* Sidebar - Progress & Stats */} -
-
- -
-
-
- -
- Kapadokya Efsanesi -
- -
-

- ROTANIZI
TASARLAYIN -

-

- "Her durak bir hikaye, her an bir anı. Size özel kurgulanmış seyahat mimarisi." -

-
- -
- {STEPS.map((step, i) => { - const Icon = step.icon; - const isActive = i === currentStep; - const isCompleted = i < currentStep; - - return ( -
-
- -
-
-
Step 0{i+1}
-
{step.title}
-
-
- ); - })} -
-
- -
-
-
- Mükemmellik Oranı - 100% -
-
-
-
-
-
-
- - {/* Main Form Area */} -
-
-
- - - -
-

- {STEPS[currentStep].description} -

-

- Lütfen tercihlerinizi belirleyin, biz sizin için kurgulayalım. -

-
- -
- {currentStep === 0 && ( - ( - - - - - - )} - /> - )} - - {currentStep === 1 && ( - ( - - - - - - )} - /> - )} - - {currentStep === 2 && ( - ( - - - - - - )} - /> - )} - - {currentStep === 3 && ( - ( - -
- - {field.value.length}/6 Seçildi -
- - -
- )} - /> - )} -
-
-
- - {/* Navigation Controls */} -
- - - {currentStep < STEPS.length - 1 ? ( - - ) : ( - - )} -
-
- -
-
-
- ); -}; - -export default memo(PlannerPage); \ No newline at end of file +export const LOADING_STEPS = [ + { label: 'Tercihleriniz analiz ediliyor...', progress: 0 }, + { label: 'Rotanız oluşturuluyor...', progress: 33 }, + { label: 'Mekanlar seçiliyor...', progress: 66 }, + { label: 'Son rötuşlar yapılıyor...', progress: 90 }, +] as const; \ No newline at end of file