24 KiB
Profesyonel SaaS Analiz Raporu
Wanderlog-Style Seyahat Planlama Uygulaması
Tarih: 5 Şubat 2026
Analiz Kapsamı: Frontend, Backend, Veritabanı, UX, Güvenlik, Performans
📊 Genel Durum Özeti
✅ Güçlü Yönler
- Temiz Kod Yapısı: TypeScript + React + Supabase stack iyi organize edilmiş
- Veritabanı Tasarımı: 41 migration ile evrimleşmiş, RLS politikaları mevcut
- Özellik Zenginliği: Trip planning, AI suggestions, provider marketplace, admin panel
- Lint Başarılı: Kod kalitesi kontrolleri geçiyor
- Balon Yönetimi: Trip-level constraint doğru implement edilmiş
- Otel Başlangıç Noktası: Doğru şekilde trip.start_location olarak saklanıyor
⚠️ Kritik İyileştirme Gereken Alanlar
- GDPR Uyumluluğu: Lead sistemi eksik (detaylar aşağıda)
- Güvenlik: Rate limiting, spam prevention yok
- Profesyonel SaaS Özellikleri: Analytics, monitoring, email notifications eksik
- Hata Yönetimi: Error boundaries ve fallback logic eksik
- UX İyileştirmeleri: Wizard flow, smart banners, loading states
🔴 KRİTİK SORUNLAR (Öncelik: Yüksek)
1. GDPR ve Veri Güvenliği Eksiklikleri
Mevcut Durum
-- leads tablosu (mevcut)
CREATE TABLE leads (
id UUID PRIMARY KEY,
email TEXT NOT NULL,
whatsapp TEXT NOT NULL,
consent_given BOOLEAN DEFAULT false,
...
);
Sorunlar
- ❌ Consent timestamp yok (ne zaman onay verildi?)
- ❌ IP adresi kaydı yok (GDPR gereksinimi)
- ❌ User agent kaydı yok
- ❌ Marketing consent ayrımı yok (data sharing vs marketing)
- ❌ Email ve WhatsApp şifrelenmemiş (plain text)
- ❌ Audit log yok (kim, ne zaman, hangi işlemi yaptı?)
- ❌ Data retention policy yok (veriler ne kadar saklanacak?)
- ❌ Right to be forgotten (GDPR Madde 17) implementasyonu yok
Önerilen Çözüm
1. Database Migration Ekle:
-- Migration: add_gdpr_compliance_to_leads.sql
ALTER TABLE leads
ADD COLUMN marketing_consent BOOLEAN DEFAULT false,
ADD COLUMN data_sharing_consent BOOLEAN DEFAULT false,
ADD COLUMN terms_accepted BOOLEAN DEFAULT false,
ADD COLUMN consent_timestamp TIMESTAMPTZ,
ADD COLUMN consent_ip_address TEXT,
ADD COLUMN consent_user_agent TEXT,
ADD COLUMN data_retention_until DATE,
ADD COLUMN anonymized_at TIMESTAMPTZ NULL,
ADD COLUMN deletion_requested_at TIMESTAMPTZ NULL;
-- Constraint: consent_given requires all sub-consents
ALTER TABLE leads
ADD CONSTRAINT check_consent_requirements
CHECK (
(consent_given = false) OR
(consent_given = true AND
data_sharing_consent = true AND
terms_accepted = true AND
consent_timestamp IS NOT NULL AND
consent_ip_address IS NOT NULL)
);
-- Audit log tablosu
CREATE TABLE lead_audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
action TEXT NOT NULL, -- 'created', 'viewed', 'purchased', 'anonymized', 'deleted'
actor_id UUID REFERENCES auth.users(id),
actor_role TEXT, -- 'user', 'provider', 'admin'
ip_address TEXT,
user_agent TEXT,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_lead_audit_logs_lead_id ON lead_audit_logs(lead_id);
CREATE INDEX idx_lead_audit_logs_created_at ON lead_audit_logs(created_at DESC);
-- RLS policies
ALTER TABLE lead_audit_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Admins can view all audit logs"
ON lead_audit_logs FOR SELECT
USING (
EXISTS (
SELECT 1 FROM profiles
WHERE profiles.id = auth.uid()
AND profiles.role = 'admin'
)
);
2. API Güncelleme (src/db/api.ts):
// Lead oluşturma - GDPR uyumlu
async create(leadData: {
trip_id: string;
email: string;
whatsapp: string;
country: string;
consent: {
marketing: boolean;
data_sharing: boolean;
terms: boolean;
ip_address: string;
user_agent: string;
};
}) {
// 1. Validation
if (!leadData.consent.data_sharing || !leadData.consent.terms) {
throw new Error('CONSENT_REQUIRED');
}
// 2. Rate limiting check (max 3 leads per email per 24h)
const { data: recentLeads } = await supabase
.from('leads')
.select('id')
.eq('email', leadData.email)
.gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
if (recentLeads && recentLeads.length >= 3) {
throw new Error('RATE_LIMIT_EXCEEDED');
}
// 3. Data retention date (90 days from now)
const retentionDate = new Date();
retentionDate.setDate(retentionDate.getDate() + 90);
// 4. Create lead
const { data: lead, error } = await supabase
.from('leads')
.insert({
...leadData,
consent_given: true,
marketing_consent: leadData.consent.marketing,
data_sharing_consent: leadData.consent.data_sharing,
terms_accepted: leadData.consent.terms,
consent_timestamp: new Date().toISOString(),
consent_ip_address: leadData.consent.ip_address,
consent_user_agent: leadData.consent.user_agent,
data_retention_until: retentionDate.toISOString().split('T')[0],
})
.select()
.single();
if (error) throw error;
// 5. Audit log
await supabase
.from('lead_audit_logs')
.insert({
lead_id: lead.id,
action: 'created',
actor_id: leadData.user_id || null,
actor_role: 'user',
ip_address: leadData.consent.ip_address,
user_agent: leadData.consent.user_agent,
metadata: {
trip_id: leadData.trip_id,
destination: leadData.destination,
},
});
return lead;
},
// Right to be forgotten (GDPR Article 17)
async anonymizeLead(leadId: string) {
const { data, error } = await supabase
.from('leads')
.update({
email: 'anonymized@example.com',
whatsapp: 'ANONYMIZED',
country: 'XX',
anonymized_at: new Date().toISOString(),
})
.eq('id', leadId)
.select()
.single();
if (error) throw error;
// Audit log
await supabase
.from('lead_audit_logs')
.insert({
lead_id: leadId,
action: 'anonymized',
actor_id: auth.uid(),
actor_role: 'admin',
metadata: { reason: 'GDPR_REQUEST' },
});
return data;
},
3. Frontend Güncelleme (LeadCaptureModal.tsx):
// IP adresi ve user agent al
const getClientInfo = async () => {
try {
const response = await fetch('https://api.ipify.org?format=json');
const { ip } = await response.json();
return {
ip_address: ip,
user_agent: navigator.userAgent,
};
} catch {
return {
ip_address: 'unknown',
user_agent: navigator.userAgent,
};
}
};
// Form submit
const onSubmit = async (values: FormValues) => {
const clientInfo = await getClientInfo();
await leadsApi.create({
...values,
consent: {
marketing: values.marketingConsent,
data_sharing: values.dataSharing,
terms: values.termsAccepted,
ip_address: clientInfo.ip_address,
user_agent: clientInfo.user_agent,
},
});
};
4. Admin Panel - GDPR Yönetimi:
// src/pages/admin/GDPRManagement.tsx
- Lead anonymization tool
- Data export (JSON/CSV)
- Consent history viewer
- Audit log viewer
- Data retention policy dashboard
2. Rate Limiting ve Spam Önleme
Sorun
- Herhangi biri sınırsız lead oluşturabilir
- DDoS saldırısına açık
- Spam email/whatsapp ile sistem kirletilebilir
Çözüm
1. Database Function (RLS ile entegre):
-- Rate limiting function
CREATE OR REPLACE FUNCTION check_lead_rate_limit(p_email TEXT)
RETURNS BOOLEAN AS $$
DECLARE
recent_count INTEGER;
BEGIN
SELECT COUNT(*)
INTO recent_count
FROM leads
WHERE email = p_email
AND created_at > NOW() - INTERVAL '24 hours';
RETURN recent_count < 3;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- RLS policy ile entegre
CREATE POLICY "Rate limit lead creation"
ON leads FOR INSERT
WITH CHECK (
consent_given = true AND
check_lead_rate_limit(email)
);
2. Edge Function Rate Limiting:
// supabase/functions/_shared/rate-limiter.ts
import { createClient } from '@supabase/supabase-js';
export async function checkRateLimit(
identifier: string, // email or IP
limit: number,
windowMinutes: number
): Promise<{ allowed: boolean; remaining: number }> {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
const windowStart = new Date(Date.now() - windowMinutes * 60 * 1000);
const { data, error } = await supabase
.from('rate_limit_log')
.select('id')
.eq('identifier', identifier)
.gte('created_at', windowStart.toISOString());
if (error) throw error;
const count = data?.length || 0;
const allowed = count < limit;
const remaining = Math.max(0, limit - count);
if (allowed) {
// Log this request
await supabase
.from('rate_limit_log')
.insert({ identifier, created_at: new Date().toISOString() });
}
return { allowed, remaining };
}
3. Provider Matching Fallback Logic Eksik
Sorun
analyze-trip Edge Function'da provider matching var ama:
- Uygun provider bulunamazsa ne olur?
- Tüm provider'lar inactive ise?
- Bölge için hiç provider yoksa?
Çözüm
analyze-trip/index.ts güncelleme:
// Provider matching with fallback
const matchProviders = async (dailyTourSlug: string, regionSlug: string) => {
// 1. Try exact match (service + region + active)
let { data: providers } = await supabase
.from('providers')
.select('*')
.contains('services', [dailyTourSlug])
.eq('region_slug', regionSlug)
.eq('is_active', true)
.order('rating', { ascending: false })
.limit(3);
if (providers && providers.length > 0) {
return {
providers,
match_type: 'exact',
confidence: 0.9,
};
}
// 2. Fallback: General guide service in region
({ data: providers } = await supabase
.from('providers')
.select('*')
.contains('services', ['private_guide'])
.eq('region_slug', regionSlug)
.eq('is_active', true)
.order('rating', { ascending: false })
.limit(3));
if (providers && providers.length > 0) {
return {
providers,
match_type: 'general_guide',
confidence: 0.7,
};
}
// 3. Fallback: Any active provider in region
({ data: providers } = await supabase
.from('providers')
.select('*')
.eq('region_slug', regionSlug)
.eq('is_active', true)
.order('rating', { ascending: false })
.limit(3));
if (providers && providers.length > 0) {
return {
providers,
match_type: 'regional',
confidence: 0.5,
};
}
// 4. No providers available
return {
providers: [],
match_type: 'none',
confidence: 0,
fallback_message: 'Şu anda bu bölge için aktif hizmet sağlayıcı bulunmamaktadır.',
};
};
🟡 ORTA ÖNCELİKLİ İYİLEŞTİRMELER
4. Error Handling ve Boundaries
Sorun
- API çağrıları başarısız olduğunda kullanıcı deneyimi kötü
- Edge Function hataları generic
- React error boundaries eksik
Çözüm
1. React Error Boundary:
// src/components/ErrorBoundary.tsx
import React from 'react';
import { AlertCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// TODO: Send to error monitoring service (Sentry)
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex flex-col items-center justify-center min-h-[400px] p-8">
<AlertCircle className="w-16 h-16 text-destructive mb-4" />
<h2 className="text-2xl font-bold mb-2">Bir şeyler ters gitti</h2>
<p className="text-muted-foreground mb-4 text-center max-w-md">
Üzgünüz, beklenmeyen bir hata oluştu. Lütfen sayfayı yenileyin veya daha sonra tekrar deneyin.
</p>
<Button onClick={() => window.location.reload()}>
Sayfayı Yenile
</Button>
</div>
);
}
return this.props.children;
}
}
2. API Error Wrapper:
// src/lib/api-error-handler.ts
export class APIError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number,
public details?: any
) {
super(message);
this.name = 'APIError';
}
}
export async function handleAPICall<T>(
apiCall: () => Promise<T>,
errorContext: string
): Promise<T> {
try {
return await apiCall();
} catch (error: any) {
console.error(`${errorContext} failed:`, error);
// Supabase error
if (error.code) {
throw new APIError(
error.message || 'Veritabanı hatası',
error.code,
500,
error
);
}
// Network error
if (error.message?.includes('fetch')) {
throw new APIError(
'Bağlantı hatası. Lütfen internet bağlantınızı kontrol edin.',
'NETWORK_ERROR',
0
);
}
// Generic error
throw new APIError(
'Beklenmeyen bir hata oluştu',
'UNKNOWN_ERROR',
500,
error
);
}
}
// Kullanım
const trips = await handleAPICall(
() => tripsApi.getAll(),
'Fetch trips'
);
3. Edge Function Error Response:
// supabase/functions/_shared/error-handler.ts
export function createErrorResponse(
error: any,
context: string
): Response {
console.error(`${context} error:`, error);
let statusCode = 500;
let errorCode = 'INTERNAL_ERROR';
let message = 'Bir hata oluştu';
// OpenAI API errors
if (error.status === 429) {
statusCode = 429;
errorCode = 'RATE_LIMIT';
message = 'Çok fazla istek. Lütfen daha sonra tekrar deneyin.';
} else if (error.status === 401) {
statusCode = 500;
errorCode = 'API_AUTH_ERROR';
message = 'API kimlik doğrulama hatası';
}
// Supabase errors
if (error.code === 'PGRST116') {
statusCode = 404;
errorCode = 'NOT_FOUND';
message = 'Kayıt bulunamadı';
}
return new Response(
JSON.stringify({
error: {
code: errorCode,
message,
details: process.env.NODE_ENV === 'development' ? error : undefined,
},
}),
{
status: statusCode,
headers: { 'Content-Type': 'application/json', ...corsHeaders },
}
);
}
5. UX İyileştirmeleri
A. Create Trip - Wizard Flow
Sorun: Tek sayfada çok fazla form alanı, kullanıcıyı bunaltıyor.
Çözüm:
// src/components/trip/CreateTripWizard.tsx
const steps = [
{
id: 'destination',
title: 'Nereye gidiyorsunuz?',
fields: ['destination', 'dates', 'travelers'],
},
{
id: 'interests',
title: 'İlgi alanlarınız neler?',
fields: ['interests'],
optional: true,
},
{
id: 'accommodation',
title: 'Konaklama bilgisi',
fields: ['hotel', 'startLocation'],
optional: true,
},
];
// Multi-step form with progress indicator
// Save progress to localStorage
// Allow skip optional steps
B. AI Banner - Smart Dismissal
Sorun: AI tour önerisi her gün için gösterilirse rahatsız edici.
Çözüm:
// src/lib/ai-banner-logic.ts
export function shouldShowAIBanner(
trip: Trip,
day: TripDay,
userProfile: UserProfile
): boolean {
// 1. User dismissed this banner?
const dismissKey = `ai-banner-dismissed-${trip.id}-${day.id}`;
if (localStorage.getItem(dismissKey)) return false;
// 2. Already created lead for this day?
if (trip.leads?.some(l => l.trip_day_id === day.id)) return false;
// 3. New user? (Don't show for first 2 trips)
if (userProfile.trip_count <= 2) return false;
// 4. Day has enough places? (min 4)
if (day.places.length < 4) return false;
// 5. User recently active? (Don't interrupt)
const lastActivity = getLastActivityTime(trip.id);
if (Date.now() - lastActivity < 5 * 60 * 1000) return false;
// 6. AI suggestion available?
return true;
}
// Show banner with delay (10 seconds after page load)
// Smooth animation
// Clear dismiss action
C. Drag & Drop - Better Visual Feedback
Çözüm:
// Enhanced drag overlay
<DragOverlay>
{activeId ? (
<div className="opacity-80 scale-105 rotate-2 shadow-2xl">
<TimelinePlace place={activePlaceData} isDragging />
</div>
) : null}
</DragOverlay>
// Drop zone indicator
<div className={cn(
"h-2 transition-all duration-200",
isOver && "h-16 bg-primary/10 border-2 border-primary border-dashed rounded-lg"
)}>
{isOver && (
<div className="flex items-center justify-center h-full text-primary text-sm font-medium">
↓ Buraya bırakın
</div>
)}
</div>
🟢 DÜŞÜK ÖNCELİKLİ İYİLEŞTİRMELER
6. Type Safety
Sorun: Bazı yerlerde any kullanılmış.
Çözüm:
// TripPlanner.tsx - line 115
- const [trip, setTrip] = useState<any>(null);
+ const [trip, setTrip] = useState<Trip | null>(null);
// Define proper types in src/types/index.ts
export interface Trip {
id: string;
user_id: string | null;
title: string;
destination: string;
start_date: string;
end_date: string;
has_balloon?: boolean;
balloon_day_id?: string | null;
start_location_type?: 'hotel' | 'custom' | 'city_center';
start_location_name?: string;
start_lat?: number;
start_lng?: number;
is_public: boolean;
public_slug?: string;
interests?: string[];
days?: TripDay[];
created_at: string;
updated_at: string;
}
🚀 PROFESYONEL SAAS ÖZELLİKLERİ (Eksik)
7. Analytics ve Monitoring
Eksik Özellikler:
-
User Behavior Tracking
- Hangi sayfalar en çok ziyaret ediliyor?
- Kullanıcılar nerede takılıyor?
- Conversion funnel analizi
-
Performance Monitoring
- API response times
- Database query performance
- Frontend rendering metrics
-
Error Monitoring
- Sentry entegrasyonu
- Error rate tracking
- User impact analysis
Önerilen Çözüm:
// 1. Sentry Integration
// src/lib/sentry.ts
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.VITE_ENV,
tracesSampleRate: 1.0,
integrations: [
new Sentry.BrowserTracing(),
new Sentry.Replay(),
],
});
// 2. Analytics (Plausible or PostHog)
// src/lib/analytics.ts
export const trackEvent = (eventName: string, properties?: Record<string, any>) => {
if (window.plausible) {
window.plausible(eventName, { props: properties });
}
};
// Usage
trackEvent('trip_created', { destination: 'Cappadocia', days: 3 });
trackEvent('lead_captured', { source: 'ai_banner' });
trackEvent('provider_contacted', { provider_id: 'xxx' });
8. Email Notifications
Eksik Özellikler:
- Lead oluşturulduğunda kullanıcıya onay emaili
- Provider'a yeni lead bildirimi
- Admin'e günlük özet raporu
- Trip reminder (seyahat 1 gün önce)
Önerilen Çözüm:
// Edge Function: send-email
// supabase/functions/send-email/index.ts
import { Resend } from 'npm:resend@2.0.0';
const resend = new Resend(Deno.env.get('RESEND_API_KEY'));
// Email templates
const templates = {
lead_confirmation: (data: any) => ({
subject: 'Seyahat planınız alındı!',
html: `
<h1>Merhaba ${data.name}!</h1>
<p>${data.destination} seyahatiniz için talebiniz alındı.</p>
<p>Yerel hizmet sağlayıcılar en kısa sürede sizinle iletişime geçecek.</p>
`,
}),
provider_new_lead: (data: any) => ({
subject: 'Yeni müşteri talebi!',
html: `
<h1>Yeni Lead!</h1>
<p><strong>Destinasyon:</strong> ${data.destination}</p>
<p><strong>Tarih:</strong> ${data.dates}</p>
<p><strong>Kişi sayısı:</strong> ${data.travelers}</p>
<a href="${data.lead_url}">Detayları Görüntüle</a>
`,
}),
};
// Send email function
Deno.serve(async (req) => {
const { template, to, data } = await req.json();
const emailContent = templates[template](data);
const { data: result, error } = await resend.emails.send({
from: 'noreply@yourdomain.com',
to,
...emailContent,
});
if (error) {
return new Response(JSON.stringify({ error }), { status: 500 });
}
return new Response(JSON.stringify({ success: true, id: result.id }));
});
9. Payment Integration (Premium Features)
Eksik Özellikler:
- Provider'lar için premium subscription
- Lead satın alma sistemi (şu anda credit-based ama ödeme yok)
- Commission tracking
Önerilen Çözüm:
// Stripe Integration
// src/lib/stripe.ts
import { loadStripe } from '@stripe/stripe-js';
export const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY);
// Edge Function: create-checkout-session
// Provider premium subscription veya lead purchase için
10. Multi-language Support
Mevcut Durum: Sadece Türkçe
Önerilen Çözüm:
// i18n setup with react-i18next
// Support: TR, EN, DE, RU (Cappadocia için önemli)
11. Provider Verification (KYC)
Eksik Özellikler:
- Kimlik doğrulama
- İşletme belgesi kontrolü
- Referans kontrolü
- Rating ve review sistemi
12. Backup ve Data Export
Eksik Özellikler:
- Kullanıcılar kendi verilerini export edebilmeli (GDPR)
- Admin backup sistemi
- Database snapshot'ları
📋 ÖNCELİKLENDİRİLMİŞ EYLEM PLANI
Faz 1: Kritik Güvenlik ve Uyumluluk (1-2 Hafta)
- ✅ GDPR compliance (lead sistemi)
- ✅ Rate limiting
- ✅ Error boundaries
- ✅ Provider matching fallback
Faz 2: UX İyileştirmeleri (1 Hafta)
- ✅ Create trip wizard
- ✅ Smart AI banner
- ✅ Better drag & drop feedback
- ✅ Loading states
Faz 3: Profesyonel SaaS Özellikleri (2-3 Hafta)
- ✅ Analytics (Plausible/PostHog)
- ✅ Error monitoring (Sentry)
- ✅ Email notifications (Resend)
- ✅ Performance monitoring
Faz 4: Gelir Modeli (2 Hafta)
- ✅ Stripe integration
- ✅ Provider subscription plans
- ✅ Commission tracking
Faz 5: Ölçeklenebilirlik (Sürekli)
- ✅ Multi-language
- ✅ Provider KYC
- ✅ Backup system
- ✅ API rate limiting
🎯 SONUÇ
Genel Değerlendirme
Uygulama iyi bir temel üzerine kurulmuş ve çoğu özellik çalışıyor. Ancak profesyonel bir SaaS ürünü olması için:
- Güvenlik ve Uyumluluk (GDPR, rate limiting) kritik öncelik
- Monitoring ve Analytics olmadan ürünü optimize edemezsiniz
- Email notifications kullanıcı deneyimi için şart
- Payment integration gelir modeli için gerekli
Tahmini Geliştirme Süresi
- Minimum Viable Professional SaaS: 4-6 hafta
- Full-featured Enterprise SaaS: 8-12 hafta
Maliyet Tahminleri (Aylık)
- Sentry (Error monitoring): $26/ay (Team plan)
- Plausible (Analytics): $9/ay (10k pageviews)
- Resend (Email): $20/ay (50k emails)
- Stripe (Payment): 2.9% + $0.30 per transaction
- Supabase (Database): $25/ay (Pro plan)
- Total: ~$80-100/ay + transaction fees
📞 Sonraki Adımlar
- Bu raporu inceleyin ve öncelikleri belirleyin
- Hangi fazdan başlamak istediğinize karar verin
- Detaylı implementation için bana bildirin
- Test ve deployment stratejisi oluşturalım
Sorularınız veya tartışmak istediğiniz noktalar var mı?