OneTrip - Seyahat Planlama Uygulaması
OneTrip, çoklu durak seyahat planlaması için geliştirilmiş kapsamlı bir web uygulamasıdır. Kullanıcılar rotalar oluşturabilir, bütçe takibi yapabilir, aktiviteler ekleyebilir ve seyahatlerini işbirlikçi şekilde planlayabilir.
🎯 Proje Özeti
| Özellik | Açıklama |
|---|---|
| Platform | Web (React SPA) |
| Backend | Supabase (PostgreSQL + Edge Functions) |
| Hosting | Netlify / Lovable / Vercel |
| Dil Desteği | Türkçe, İngilizce, Almanca |
| Tema Desteği | 6 farklı tema (Ocean, Forest, Sunset, Aurora, City, Desert) |
📁 Proje Yapısı
onetrip/
├── src/
│ ├── auth/ # Kimlik doğrulama
│ │ └── AuthProvider.tsx # Auth context ve hooks
│ │
│ ├── components/ # React bileşenleri
│ │ ├── ui/ # shadcn/ui bileşenleri
│ │ ├── Topbar.tsx # Üst navigasyon
│ │ ├── PlannerSidebar.tsx # Sol sidebar
│ │ ├── StopsTimeline.tsx # Durak zaman çizelgesi
│ │ ├── StopSidebar.tsx # Durak detay paneli
│ │ ├── BudgetPanel.tsx # Bütçe özeti
│ │ ├── FloatingActivityEditor.tsx # Aktivite ekleme modalı
│ │ ├── ExpensePaymentSelector.tsx # Masraf paylaşım seçici
│ │ ├── CollaboratorsList.tsx # İşbirlikçi listesi
│ │ ├── PackingList.tsx # Bavul listesi
│ │ └── ...
│ │
│ ├── hooks/ # Custom React hooks
│ │ ├── useRealtimeTrip.ts # Realtime senkronizasyon
│ │ ├── useDebouncedAutosave.ts # Otomatik kayıt
│ │ ├── useCollaborativeEditing.ts # Çoklu düzenleme
│ │ ├── useCityImage.ts # Unsplash entegrasyonu
│ │ ├── useTheme.ts # Tema yönetimi
│ │ └── ...
│ │
│ ├── lib/ # Yardımcı fonksiyonlar
│ │ ├── types.ts # TypeScript tip tanımları
│ │ ├── trips.ts # Trip CRUD işlemleri
│ │ ├── expenses.ts # Masraf hesaplamaları
│ │ ├── storage.ts # LocalStorage yönetimi
│ │ ├── budget.ts # Bütçe hesaplamaları
│ │ └── utils.ts # Genel yardımcılar
│ │
│ ├── pages/ # Sayfa bileşenleri
│ │ ├── Landing.tsx # Ana sayfa
│ │ ├── Planner.tsx # Ana planlama sayfası (1780+ satır)
│ │ ├── Auth.tsx # Giriş/Kayıt
│ │ ├── Profile.tsx # Profil sayfası
│ │ ├── PublicTrip.tsx # Herkese açık trip görüntüleme
│ │ └── ...
│ │
│ ├── integrations/supabase/ # Supabase istemci konfigürasyonu
│ │ ├── client.ts # ⚠️ OTOMATİK - düzenlemeyin
│ │ └── types.ts # ⚠️ OTOMATİK - düzenlemeyin
│ │
│ ├── i18n.tsx # Çoklu dil sistemi
│ ├── index.css # Global stiller + tema tanımları
│ ├── App.tsx # Route tanımları
│ └── main.tsx # Uygulama giriş noktası
│
├── supabase/
│ ├── functions/ # Edge Functions (Deno)
│ │ ├── city-suggestions/ # AI şehir önerileri
│ │ ├── calculate-distance/ # Mesafe hesaplama
│ │ ├── send-auth-email/ # Kimlik e-postaları
│ │ ├── send-trip-invite/ # Davet e-postaları
│ │ ├── unsplash-image/ # Unsplash API proxy
│ │ └── ...
│ │
│ ├── migrations/ # Veritabanı migrasyonları
│ └── config.toml # Supabase yapılandırması
│
├── public/
│ └── assets/ # Statik dosyalar (görseller, temalar)
│
├── docs/ # Ek dokümantasyon
│ ├── ARCHITECTURE.md # Mimari dokümantasyonu
│ ├── API.md # API referansı
│ ├── COMPONENTS.md # Bileşen dokümantasyonu
│ ├── EDGE_FUNCTIONS.md # Edge Functions rehberi
│ ├── MIGRATION.md # Migration rehberi
│ └── email-templates.md # E-posta şablonları
│
└── [config files] # Yapılandırma dosyaları
🗄️ Veritabanı Şeması
Ana Tablolar
trips - Seyahat Planları
id UUID PRIMARY KEY
user_id UUID NOT NULL (sahip)
title TEXT
currency TEXT DEFAULT 'USD'
participants INTEGER DEFAULT 1
data JSONB (tüm trip verisi)
short_code TEXT UNIQUE (paylaşım kodu)
is_public BOOLEAN DEFAULT false
verification_code TEXT (PDF doğrulama)
created_at TIMESTAMPTZ
updated_at TIMESTAMPTZ
trip_collaborators - İşbirlikçiler
id UUID PRIMARY KEY
trip_id UUID REFERENCES trips
user_id UUID (kayıtlı kullanıcı)
invited_email TEXT (davetli e-posta)
role TEXT ('viewer' | 'editor' | 'owner')
created_at TIMESTAMPTZ
trip_expenses - Masraflar
id UUID PRIMARY KEY
trip_id UUID REFERENCES trips
paid_by TEXT (ödeme yapan kişi adı)
amount NUMERIC
description TEXT
category TEXT
split_type TEXT ('equal' | 'custom' | 'percentage')
participants JSONB (paylaşanlar)
split_details JSONB (özel paylaşım detayları)
expense_date DATE
trip_participants - Katılımcılar
id UUID PRIMARY KEY
trip_id UUID REFERENCES trips
name TEXT
user_id UUID (opsiyonel - kayıtlı kullanıcı)
debt_settlements - Borç Ödemeleri
id UUID PRIMARY KEY
trip_id UUID REFERENCES trips
from_participant TEXT
to_participant TEXT
amount NUMERIC
notes TEXT
profiles - Kullanıcı Profilleri
id UUID PRIMARY KEY (= auth.users.id)
email TEXT
full_name TEXT
avatar_url TEXT
bio TEXT
preferred_language TEXT DEFAULT 'en'
has_completed_onboarding BOOLEAN
email_verified BOOLEAN
last_trip_id UUID (son çalışılan trip)
notifications - Bildirimler
id UUID PRIMARY KEY
user_id UUID
trip_id UUID
type TEXT ('trip_invite' | 'collaboration_invite' | ...)
title TEXT
message TEXT
is_read BOOLEAN
metadata JSONB
Önbellek Tabloları
city_suggestions: AI şehir önerilerinin önbelleğidistance_cache: Şehirler arası mesafe hesaplamaları
Diğer
user_roles: Admin rolleriaudit_logs: Admin işlem loglarıpage_visits: Sayfa ziyareti analitikleriemail_verification_tokens: E-posta doğrulama tokenları
🔐 RLS (Row Level Security) Politikaları
Tüm tablolarda RLS aktiftir. Temel prensipler:
- trips: Kullanıcılar sadece kendi trip'lerini ve işbirlikçi oldukları trip'leri görebilir
- trip_collaborators: Trip sahipleri işbirlikçi ekleyip kaldırabilir
- trip_expenses: Trip üyeleri masraf ekleyebilir/düzenleyebilir
- profiles: Kullanıcılar sadece kendi profillerini ve işbirlikçilerinin profillerini görebilir
Önemli veritabanı fonksiyonları:
is_trip_collaborator(trip_id, user_id): Kullanıcının işbirlikçi olup olmadığını kontrol edercan_invite_to_trip(trip_id, user_id): Davet yetkisi kontrolücan_accept_trip_invite(trip_id, user_id): Davet kabul yetkisi
🧩 Temel Veri Tipleri (TypeScript)
Trip
type Trip = {
id: string
title: string
currency: string
participants: number
stops: Stop[]
legs: Leg[]
generalNotes?: string
packingList?: PackingItem[]
short_code?: string
// Resmi belge bilgileri
travelerName?: string
passportNumber?: string
nationality?: string
verificationCode?: string
}
Stop (Durak)
type Stop = {
id: string
city: string
arrivalDate?: string // ISO format
departureDate?: string
stayNights: number
notes?: string
activities: Activity[]
budget: Budget
isDayTrip?: boolean // Günübirlik mi?
dayTrips?: Stop[] // İç içe günübirlik turlar
noAccommodation?: boolean // Konaklama yok
}
Activity (Aktivite)
type Activity = {
id: string
title: string
category: string // 'culture', 'food', 'nature', etc.
cost?: number
time?: string // "14:30"
location?: string
attachments?: Attachment[]
isShared?: boolean // Maliyet paylaşılıyor mu?
completed?: boolean // Tamamlandı mı?
payment?: ExpensePayment // Kim ödedi, kimler paylaşıyor
}
Budget
type Budget = {
lodgingPerNight?: number
lodgingTotal?: number
foodPerDay?: number
other?: number
transport?: number
// Paylaşım bayrakları
isLodgingShared?: boolean // default: true
isFoodShared?: boolean // default: false
// Ödeme takibi
lodgingPayment?: ExpensePayment
foodPayment?: ExpensePayment
}
ExpensePayment
type ExpensePayment = {
paidBy?: string // Ödeyen kişi adı
participants?: string[] // Paylaşanlar (boş = herkes)
amount?: number
}
⚡ Edge Functions
Tüm edge functions supabase/functions/ altındadır ve Deno runtime kullanır.
| Fonksiyon | Açıklama | API Key |
|---|---|---|
city-suggestions | AI ile şehir aktivite önerileri | OpenAI |
calculate-distance | Şehirler arası mesafe | OpenAI |
packing-suggestions | AI bavul önerileri | OpenAI |
generate-route-plan | Akıllı rota planlama | OpenAI |
unsplash-image | Şehir görselleri | Unsplash |
send-auth-email | Kayıt/şifre sıfırlama e-postaları | Resend |
send-trip-invite | Trip davet e-postaları | Resend |
send-contact-email | İletişim formu | Resend |
validate-city-name | Şehir adı doğrulama | OpenAI |
verify-trip | PDF doğrulama kodu üretimi | - |
search-profiles | Kullanıcı arama | - |
get-invited-trip | Davetli trip bilgisi | - |
Edge Function Yapısı
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
// Auth kontrolü
const authHeader = req.headers.get("Authorization");
// ...
});
🔄 Realtime Senkronizasyon
Uygulama Supabase Realtime kullanarak çoklu kullanıcı desteği sağlar.
useRealtimeTrip Hook
const { activeUsers } = useRealtimeTrip(
tripId, // Dinlenecek trip
userId, // Mevcut kullanıcı
onTripUpdate, // Trip güncellendiğinde
onRemovedFromTrip // İşbirlikçilikten çıkarıldığında
);
Özellikler:
- Presence tracking: Aktif kullanıcıları gösterir
- Trip değişiklik dinleme:
postgres_changesile - İşbirlikçi kaldırma dinleme: Kullanıcı çıkarılırsa trip kopyalanır
useCollaborativeEditing Hook
Hangi kullanıcının hangi alanı düzenlediğini takip eder (collision prevention).
💾 Otomatik Kayıt Sistemi
useDebouncedAutosave Hook
useDebouncedAutosave({
data: trip,
user: user,
saveAction: saveTrip,
delay: 1500, // 1.5 saniye debounce
onShortCodeGenerated: (code) => {},
onPermissionError: () => {} // RLS hatası durumu
});
Kayıt Akışı:
tripstate değişir- 1.5 saniye beklenir (debounce)
saveTrip()Supabase'e kaydedershort_codeyoksa otomatik üretilir- LocalStorage'a da yedeklenir
🎨 Tema Sistemi
Tema Değişkenleri (index.css)
body[data-theme="ocean"] {
--color-brand: #06B6D4;
--background: 192 90% 97%;
--primary: 195 98% 45%;
--gradient-brand: linear-gradient(135deg, #06B6D4 0%, #0EA5E9 50%, #3B82F6 100%);
/* ... */
}
useTheme Hook
const { theme, setTheme } = useTheme();
// theme: 'default' | 'ocean' | 'sunset' | 'forest' | 'aurora' | 'city' | 'desert'
💰 Masraf Hesaplama Sistemi
calculateParticipantBalances()
Her katılımcının ne kadar ödediğini ve borçlu olduğunu hesaplar.
calculateSimplifiedDebts()
Greedy algoritma ile minimum transfer sayısını hesaplar.
// Örnek çıktı
[
{ from: 'Ali', to: 'Mehmet', amount: 150 },
{ from: 'Ayşe', to: 'Mehmet', amount: 75 }
]
calculateDebtsWithSettlements()
Yapılan ödemeleri düşerek kalan borcu hesaplar.
🌐 Çoklu Dil Sistemi
useI18n Hook
const { t, lang, setLang } = useI18n();
// Kullanım
t('planner.addStop') // "Durak Ekle"
t('common.save') // "Kaydet"
Dil dosyası: src/languages.json
🔧 Ortam Değişkenleri
.env (Otomatik - düzenlemeyin)
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=eyJ...
VITE_SUPABASE_PROJECT_ID=xxx
Supabase Secrets (Edge Functions için)
openai_api- OpenAI API KeyUNSPLASH_API_KEY- Unsplash API KeyRESEND_API_KEY- Resend e-posta servisiSUPABASE_SERVICE_ROLE_KEY- Supabase admin erişimi
🚀 Lokal Geliştirme
Gereksinimler
- Node.js 18+
- npm veya bun
Kurulum
# Repo klonlama
git clone <repo-url>
cd onetrip
# Bağımlılıkları yükleme
npm install
# veya
bun install
# Geliştirme sunucusu
npm run dev
Environment Variables
.env.example dosyasını .env olarak kopyalayın ve değerleri doldurun.
📦 Build & Deploy
Production Build
npm run build
Netlify Deploy
netlify deploy --prod
Önemli Notlar
- Edge functions otomatik deploy edilir (Supabase/Lovable Cloud)
_redirectsdosyası SPA routing için gereklidir
🔄 Migration Rehberi
Lovable'dan Bağımsız Çalışma
Etap 1: Kod + Netlify (Lovable DB ile)
- GitHub'dan kodu klonlayın
.envdosyasını Lovable değerleriyle oluşturun- Netlify'a deploy edin
- Edge functions için Lovable Supabase'i kullanmaya devam edin
Etap 2: Kendi Supabase'inize Geçiş
- Yeni Supabase projesi oluşturun
supabase/migrations/klasöründeki SQL'leri çalıştırın- Storage bucket'ları oluşturun (
avatars) - Edge functions'ı Supabase CLI ile deploy edin
- Secrets'ları yeni projede ayarlayın
.envdosyasını güncelleyin
📝 Önemli Dosyalar
| Dosya | Açıklama | Düzenlenebilir |
|---|---|---|
src/pages/Planner.tsx | Ana planlayıcı (1780+ satır) | ✅ |
src/lib/types.ts | Tip tanımları | ✅ |
src/lib/trips.ts | Trip CRUD | ✅ |
src/lib/expenses.ts | Masraf hesaplamaları | ✅ |
src/index.css | Tema ve global stiller | ✅ |
src/integrations/supabase/client.ts | Supabase istemci | ❌ OTOMATİK |
src/integrations/supabase/types.ts | DB tipleri | ❌ OTOMATİK |
supabase/migrations/* | DB şeması | ❌ READONLY |
🐛 Bilinen Sorunlar ve Çözümler
"new row violates row-level security policy"
- Kullanıcı giriş yapmamış olabilir
user_idkolonu insert'te doldurulmalı
Realtime güncellemeler gelmiyor
- Trip ID'si geçerli bir UUID olmalı
- Kullanıcı authenticated olmalı
- RLS politikaları kontrol edilmeli
Edge function timeout
- 10 saniye limiti var
- Büyük işlemler için streaming kullanın
📄 Lisans
Bu proje özel lisans altındadır. İzinsiz kullanım yasaktır.
📞 Destek
Sorularınız için: [İletişim sayfası]
Son güncelleme: Ocak 2026