
Rezervasyon Sistemi

İzmir merkezli VIP transfer firması için üç dilli tanıtım sitesi ve rezervasyon sistemi. Tek Next.js uygulamasında iki ayrı kök layout: ziyaretçiye açık, dile göre yolu değişen site ve dil segmentinin tamamen dışında duran yönetim paneli.
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.
İ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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Ne yapmak istediğinizi anlatın; ne kadar süreceğini ve nereden başlanacağını baştan söylerim.