Ana içeriğe geç
Projelere Dön
VAP Turizm logo

VAP Turizm

Rezervasyon Sistemi

VAP Turizm

İzmir'deki bir VIP transfer firması için üç dilli tanıtım sitesi ve rezervasyon paneli. Yabancı misafir siteyi kendi dilinde buluyor; her talep, verilen fiyat ve sonraki adım panelde kayıt altında.

Kullanılan Teknolojiler

  • Next.js 16
  • TypeScript
  • Drizzle ORM
  • Neon Postgres
  • next-intl
  • Zod
  • Web Push (VAPID)
  • Resend
  • GSAP

Öne Çıkan Özellikler

  • Tek uygulamada iki kök layout — site ve yönetim paneli ayrı kabuklar
  • Üç dil, dile göre yerelleştirilmiş yollar; İngilizce kökte ön eksiz
  • Sözle paylaşılabilir rezervasyon kodu — karıştırılan karakterler alfabede yok
  • Rezervasyon durumu altı adımlı ve her geçiş ayrı tabloda kayıtlı
  • Yeni rezervasyonda yöneticiye web push bildirimi
  • Anahtarı olmayan entegrasyon sessizce devre dışı kalıyor — uygulama yine ayağa kalkıyor

Geliştirme Süresi

2 ay

Problem

Transfer talebi telefon ve mesajla geliyordu; talebin kaydı, verilen fiyat ve sonrasında ne olduğu aynı yerde durmuyordu. Böyle bir işte kaybolan şey sipariş değil, siparişin geçmişi: müşteri "size şu fiyatı söylemiştiniz" dediğinde, söylenen fiyatın nerede ve ne zaman kaydedildiğine dair tek kaynak yok. İkinci mesele dil: hedef kitlenin bir kısmı Almanya'dan geliyor ve arama sonucunda kendi dilinde bir adres görmek istiyor — sayfayı çevirmek yetmiyor, adresin kendisinin de o dilde olması gerekiyor. Üçüncüsü kapsam sorusu: bir transfer sitesine ödeme entegre edilmeli mi? Ödeme, aynı zamanda iade, fatura ve uyuşmazlık demek.

Kullanıcılar

İki taraf. Ziyaretçi: çoğunlukla telefondan, çoğunlukla bir uçuş saatine bakarak geliyor; rotayı ve saati girip fiyat teklifi bekliyor, üye olmak istemiyor. Yönetici: gün içinde gelen talebi görmek, fiyat yazmak ve durumu ilerletmek istiyor; masasında değil, telefonunda. Bu yüzden yeni talep bildirimi e-postayla değil, cihazına düşen bir bildirimle geliyor.

Rolüm

Tek geliştirici. Veri modeli ve migration'lar, üç dilli rota tasarımı, rezervasyon akışı ve durum makinesi, yönetim paneli, bildirim ve e-posta katmanı, dağıtım.

Mimari

Tek Next.js 16 uygulaması, iki kök layout. Site tarafı `(site)/[locale]` altında: dil URL'in ilk parçasında, yollar next-intl'in pathname haritasıyla dile göre değişiyor ve İngilizce kökte ön eksiz duruyor. Yönetim paneli `(admin)/admin` altında ve dil segmentinin tamamen dışında — tek dilli, tek kabuk. Yazma yolu Server Action → Zod → Drizzle. Rezervasyon oluşturulurken sunucuda bir kod üretiliyor, kayıt ve ilk durum geçişi aynı istekte yazılıyor; kod çakışırsa çağıran taraf yeni kodla yeniden deniyor. Durum altı değerli bir enum (yeni, teklif verildi, ödendi, onaylandı, iptal, tamamlandı) ve her geçiş ayrı bir geçmiş tablosuna düşüyor. Fiyat kaydın içinde tam sayı olarak ve para birimiyle birlikte duruyor; ödeme uygulamanın içinde değil, kayda iliştirilen bir ödeme bağlantısı üzerinden yürüyor. Bildirim ve e-posta katmanı yazma yolunun dışında: ikisi de hata fırlatmıyor, gönderilen adet döndürüyor.

Kritik Kararlar

  1. Tek uygulama, iki kök layout — panel için ayrı proje açılmadı

    Yönetim paneli site ile aynı veri modelini, aynı doğrulama şemalarını ve aynı sorgu katmanını kullanıyor. Ayrı bir projeye alınsaydı bu üçü ya kopyalanacak ya paylaşılan bir pakete taşınacaktı; ikisi de iki kişilik olmayan bir ekipte gereksiz yük. İki kök layout, panelin site kabuğunu, dil segmentini ve pazarlama script'lerini hiç taşımamasını sağlıyor.

    ÖdünleşimPanel ile site aynı dağıtımı paylaşıyor: sitede yapılan bir değişiklik paneli de yeniden yayına alıyor ve tersi. Paketleme tarafında da dikkat gerekiyor — panelin ağır bileşenleri yanlışlıkla site tarafına sızabilir, bunu engelleyen bir mekanizma değil bir alışkanlık var.

  2. Yollar dile göre yerelleştirildi, yalnız metin çevrilmedi

    Arama sonucunda görünen adresin dili, tıklama kararını etkileyen bir sinyal. Hizmet, filo ve yasal sayfaların yolları üç dilde ayrı; iç anahtar tek kalıyor, dışa vurulan adres değişiyor. İngilizce ön eksiz kökte duruyor, böylece varsayılan dil için gereksiz bir yönlendirme katmanı oluşmuyor.

    ÖdünleşimHer yeni sayfa üç ayrı yol tanımı gerektiriyor ve bunlardan biri unutulursa hata derleme anında değil, o dilde 404 olarak ortaya çıkıyor. Yayınlanmış bir yolun sonradan değiştirilmesi de yönlendirme borcu yaratıyor.

  3. Rezervasyon kodu insan için tasarlandı

    Kod telefonda okunuyor ve mesajda yazılıyor. Alfabeden karıştırılabilir karakterler çıkarıldı, tarih parçası kaydın açıldığı yerel güne göre üretiliyor ve kod tahmin edilemeyecek rastgele bir sonek taşıyor. Böylece kod hem sözlü paylaşıma uygun hem de sıra numarası gibi ardışık değil.

    ÖdünleşimRastgelelik teorik bir çakışma ihtimali bırakıyor; benzersizlik kısıtı bunu yakalıyor ama çağıran tarafın yeniden deneme yolu taşıması gerekiyor. Kod aynı zamanda kayıt sayısını gizlediği için, "bugün kaç talep geldi" sorusu koddan okunamıyor, ayrı sorgu gerektiriyor.

  4. Ödeme uygulamanın içine alınmadı

    Rezervasyon kaydı fiyatı ve para birimini taşıyor, ödeme ise kayda iliştirilen bir bağlantı üzerinden yürüyor. Ödemeyi içeri almak yalnız bir entegrasyon değil; iade akışı, fatura, uyuşmazlık ve saklama yükümlülüğü demek. Bu hacimdeki bir işte o yükün karşılığı yok.

    ÖdünleşimÖdeme durumu sistemin dışında yaşıyor: kaydın "ödendi" olması, yöneticinin durumu elle ilerletmesine bağlı. Otomatik mutabakat yok, dolayısıyla ödendi ama işaretlenmemiş bir kayıt mümkün.

  5. Anahtarı olmayan entegrasyon sessizce kapanıyor

    Ortam değişkenleri Zod ile doğrulanıyor; zorunlu olanlar eksikse üretim derlemesi hata veriyor, opsiyonel olanlar eksikse ilgili özellik kapanıyor. Bildirim gönderimi anahtar yoksa sıfır döndürüp geçiyor, e-posta katmanı hiçbir koşulda hata fırlatmıyor. Rezervasyon yazılabildiği sürece uygulama çalışır kalıyor.

    ÖdünleşimSessiz devre dışı kalma, yanlışlıkla kapanmış bir özelliğin fark edilmemesi demek: bildirim anahtarı yanlış girilirse sistem çalışıyor görünür, yalnız bildirim gelmez. Bunu yakalayan bir sağlık kontrolü yok, yalnız günlük kaydı var.

Teknoloji Seçimi

Drizzle, enum ve migration'ları SQL'e yakın tuttuğu için durum makinesinin veritabanındaki karşılığı okunur kalıyor — altı durumun tanımı bir migration dosyasında açıkça duruyor. Neon, dağıtımla aynı serverless modelde; bu ölçekte ayrı bir veritabanı sunucusu işletmenin karşılığı yok. next-intl yol yerelleştirmesini rota katmanında çözüyor, yani çeviri dosyası ile URL yapısı aynı yerden besleniyor. Web Push'un tercih edilme sebebi e-postanın gecikmesi: yeni talebin yöneticiye ulaşma süresi bu işte doğrudan cevap süresine dönüşüyor. GSAP ve yumuşatılmış kaydırma yalnız site tarafında; panel bu paketlerin hiçbirini taşımıyor.

Güvenlik

Yönetim paneli imzalı bir oturum çerezi ile korunuyor ve parolalar bcrypt ile saklanıyor. Panel rotaları dil segmentinin dışında olduğu için site tarafındaki hiçbir yerelleştirme yolu panele düşmüyor. Rezervasyon formu Turnstile arkasında; doğrulama sunucu tarafında yapılıyor. Kişisel veri taşıyan alanlar yalnız kayıt üzerinde tutuluyor, bildirim gövdesine girmiyor — cihaza düşen bildirim yalnız kodu ve bağlantıyı taşıyor. Ölü push abonelikleri sağlayıcının "artık yok" cevabında tablodan siliniyor, böylece abonelik tablosu kullanılmayan uç adreslerle şişmiyor.

Performans

Bu proje için saklanmış, tarih damgalı bir Lighthouse çıktısı bulunmadığı için buraya sayfa hızı skoru yazılmadı. Yapısal tarafta site sayfaları statik üretiliyor ve dinamik olan tek yol rezervasyon yazma yolu; yönetim paneli tamamen dinamik. Hareket katmanı yalnız site tarafına yükleniyor, panelin paketine hiç girmiyor. Bildirim ve e-posta gönderimi yazma yolunun dışında çalıştığı için formun cevap süresi bir sağlayıcının yavaşlamasına bağlı değil.

Sonuçlar

Aktif geliştirme
2026-07 → 2026-08 · 53 commit
TypeScript kaynak dosyası
160
Şema
4 tablo · 9 migration · üç enum (durum, yolcu aralığı, gidiş türü)
Rota
20 sayfa — üç dilli site ve tek dilli yönetim paneli
Mimari garanti
Rezervasyon kaydı ve ilk durum geçişi aynı istekte yazılır; durum değişiklikleri ayrı bir geçmiş tablosunda tutulur
Mimari garanti
Opsiyonel entegrasyonun anahtarı yoksa özellik kapanır, uygulama ayağa kalkmaya devam eder

Bugün Ne Farklı Yapardım

Bu repoda otomatik test yok ve eksikliği en çok durum makinesinde hissediliyor: altı durumlu bir geçiş grafiğinde hangi geçişin meşru olduğu bugün yalnız arayüzde ve akılda duruyor, kodda zorlanmıyor. Ustura'da randevu çakışmasını veritabanı kısıtına taşıdığımda kural hangi yoldan gelinirse gelinsin geçerli olmuştu; burada da izinli geçişleri bir tabloya ya da kısıta indirir, ardından saf bir fonksiyon olarak test ederdim. İkinci nokta ödeme: kapsam dışında bırakma kararının arkasındayım, ama "ödendi" durumunun elle işaretlenmesi bir mutabakat açığı bırakıyor. Bugün en azından işaretlenme anını ve işaretleyen kullanıcıyı geçmiş tablosuna yazdırırdım — kayıt varsa tartışma biter, yoksa herkesin hatırası farklı olur.

Nereden başlayacağınızı birlikte bulalım

Ne yapmak istediğinizi anlatın; ne kadar süreceğini ve en mantıklı ilk adımı baştan söylerim.