# Expo React Native — Google & Facebook Login Entegrasyonu

Backend API'siyle birlikte çalışan tam entegrasyon kılavuzu.

---

## İçindekiler

1. [Mimari Akış](#mimari-akış)
2. [Gerekli Paketler](#gerekli-paketler)
3. [Google Sign-In Kurulumu](#google-sign-in-kurulumu)
4. [Facebook Login Kurulumu](#facebook-login-kurulumu)
5. [Backend API Referansı](#backend-api-referansı)
6. [Expo Uygulama Kodu](#expo-uygulama-kodu)
7. [Profil Tamamlama Ekranı](#profil-tamamlama-ekranı)
8. [Sıklıkla Yapılan Hatalar](#sıklıkla-yapılan-hatalar)
9. [Checklist](#checklist)

---

## Mimari Akış

```
[Expo App]                        [Laravel API]
    │                                   │
    │─── Google/FB SDK ile token al ───>│
    │                                   │
    │─── POST /api/auth/google ─────────│──> Google tokeninfo API doğrula
    │    { id_token: "..." }            │──> Kullanıcı bul veya oluştur
    │                                   │──> Sanctum token üret
    │<── { token, user,                 │
    │      profile_incomplete,          │
    │      missing_fields } ────────────│
    │                                   │
    │  [profile_incomplete === true]    │
    │─── Tamamlama ekranı göster ──────>│
    │                                   │
    │─── POST /api/auth/complete ───────│──> Profil kaydet
    │    Bearer token + form data       │
    │<── { success: true } ─────────────│
    │                                   │
    │─── Ana sayfaya geç ──────────────>│
```

---

## Gerekli Paketler

```bash
npx expo install @react-native-google-signin/google-signin
npx expo install react-native-fbsdk-next
npx expo install expo-secure-store
npx expo install @react-navigation/native @react-navigation/native-stack
npx expo install expo-build-properties
```

> **Önemli:** Bu paketler native modüller içerir — **Expo Go** ile çalışmazlar.  
> **EAS Build** (Expo Application Services) veya **bare workflow** kullanmanız gerekir.

---

## Google Sign-In Kurulumu

### 1. Google Cloud Console

1. [console.cloud.google.com](https://console.cloud.google.com) → Projenizi seçin
2. **APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID**
3. **Üç ayrı Client ID** oluşturun:
   - **Web application** → Callback URL: `https://siteniz.com/auth/google/callback`
   - **Android** → Package name: `com.sirketiniz.astrobil` + SHA-1 fingerprint
   - **iOS** → Bundle ID: `com.sirketiniz.astrobil`
4. **Web Client ID** ve **Web Client Secret**'ı admin paneline girin:  
   `/admin/social-auth-settings`

### 2. `app.json` / `app.config.js`

```json
{
  "expo": {
    "android": {
      "googleServicesFile": "./google-services.json",
      "package": "com.sirketiniz.astrobil"
    },
    "ios": {
      "googleServicesFile": "./GoogleService-Info.plist",
      "bundleIdentifier": "com.sirketiniz.astrobil"
    },
    "plugins": [
      [
        "@react-native-google-signin/google-signin",
        {
          "iosUrlScheme": "com.googleusercontent.apps.IOS_CLIENT_ID_BURAYA"
        }
      ]
    ]
  }
}
```

### 3. SHA-1 Parmak İzi (Android)

```bash
# Debug keystore (geliştirme)
keytool -keystore ~/.android/debug.keystore -list -v -alias androiddebugkey -storepass android

# EAS ile production
eas credentials
```

---

## Facebook Login Kurulumu

### 1. Facebook Geliştiriciler Konsolu

1. [developers.facebook.com](https://developers.facebook.com) → Uygulamanızı seçin
2. **Uygulama Kimliği (App ID)** ve **Uygulama Gizli Anahtarı (App Secret)** bulun
3. Admin paneline girin: `/admin/social-auth-settings`
4. **Facebook Login → Ayarlar → Geçerli OAuth Yönlendirme URI'leri** ekleyin:  
   `https://siteniz.com/auth/facebook/callback`
5. Android SHA-1 hash'i ekleyin:  
   **Ayarlar → Temel → Android → Key Hash**

```bash
# Android debug hash (macOS/Linux)
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

# Windows PowerShell
keytool -exportcert -alias androiddebugkey -keystore "$env:USERPROFILE\.android\debug.keystore" | openssl sha1 -binary | openssl base64
```

### 2. `app.json` / `app.config.js`

```json
{
  "expo": {
    "plugins": [
      [
        "react-native-fbsdk-next",
        {
          "appID": "FACEBOOK_APP_ID_BURAYA",
          "clientToken": "FACEBOOK_CLIENT_TOKEN_BURAYA",
          "displayName": "Astro Bilge",
          "scheme": "fb FACEBOOK_APP_ID_BURAYA",
          "advertiserIDCollectionEnabled": false,
          "autoLogAppEventsEnabled": false,
          "isAutoInitEnabled": true
        }
      ]
    ]
  }
}
```

> **Client Token:** Facebook Konsolu → Ayarlar → Gelişmiş → İstemci Jetonu

---

## Backend API Referansı

### Base URL

```
https://siteniz.com/api
```

### POST `/api/auth/google`

Google Sign-In SDK'dan alınan `idToken`'ı doğrular.

**Request:**
```json
{
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6..."
}
```

**Response (başarılı):**
```json
{
  "token": "1|abc123sanctumtoken...",
  "profile_incomplete": false,
  "missing_fields": [],
  "user": {
    "id": 42,
    "name": "Ahmet",
    "last_name": "Yılmaz",
    "email": "ahmet@example.com",
    "avatar": "https://siteniz.com/storage/avatars/...",
    "provider": "google",
    "gender": "male",
    "birth_date": "1990-05-15",
    "birth_time": "14:30",
    "birth_place_id": 745044
  }
}
```

**Response (profil eksik):**
```json
{
  "token": "1|abc123sanctumtoken...",
  "profile_incomplete": true,
  "missing_fields": ["birth_date", "birth_place_id", "gender"],
  "user": { ... }
}
```

**Hata (401):**
```json
{ "message": "Google kimlik doğrulaması başarısız. Token geçersiz veya süresi dolmuş." }
```

---

### POST `/api/auth/facebook`

Facebook Login SDK'dan alınan `accessToken`'ı doğrular.

**Request:**
```json
{
  "access_token": "EAAxxxxxx..."
}
```

**Response:** Google ile aynı format (`provider: "facebook"`).

---

### POST `/api/auth/complete-profile`

**Header:** `Authorization: Bearer {token}`

Social giriş sonrası eksik profil bilgilerini tamamlar.

**Request:**
```json
{
  "birth_date": "1990-05-15",
  "birth_place_id": 745044,
  "gender": "male",
  "birth_time": "14:30",
  "phone": "05551234567"
}
```

**Response (başarılı):**
```json
{
  "success": true,
  "message": "Profil bilgileriniz başarıyla kaydedildi.",
  "profile_incomplete": false
}
```

---

### GET `/api/cities/search?q=istanbul`

Doğum yeri arama — tamamlama ekranında kullanılır.

**Response:**
```json
[
  {
    "id": 745044,
    "name": "Istanbul",
    "admin1_name": "Istanbul",
    "country_code": "TR"
  }
]
```

---

## Expo Uygulama Kodu

### `src/api/auth.ts`

```typescript
const BASE_URL = 'https://siteniz.com/api';

export async function googleSocialLogin(idToken: string) {
  const res = await fetch(`${BASE_URL}/auth/google`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify({ id_token: idToken }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? 'Google giriş hatası');
  }
  return res.json();
}

export async function facebookSocialLogin(accessToken: string) {
  const res = await fetch(`${BASE_URL}/auth/facebook`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify({ access_token: accessToken }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? 'Facebook giriş hatası');
  }
  return res.json();
}

export async function completeProfile(token: string, data: {
  birth_date: string;
  birth_place_id: number;
  gender: 'male' | 'female';
  birth_time?: string;
  phone?: string;
}) {
  const res = await fetch(`${BASE_URL}/auth/complete-profile`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify(data),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? 'Profil kaydedilemedi');
  }
  return res.json();
}
```

---

### `src/screens/LoginScreen.tsx`

```tsx
import React, { useState } from 'react';
import {
  View, Text, TouchableOpacity, StyleSheet, Alert, ActivityIndicator,
} from 'react-native';
import { GoogleSignin, statusCodes } from '@react-native-google-signin/google-signin';
import { LoginManager, AccessToken } from 'react-native-fbsdk-next';
import * as SecureStore from 'expo-secure-store';
import { useNavigation } from '@react-navigation/native';
import { googleSocialLogin, facebookSocialLogin } from '../api/auth';

// ⚠️ Bu değeri Google Cloud Console'daki WEB Client ID ile değiştirin
const GOOGLE_WEB_CLIENT_ID = 'BURAYA_WEB_CLIENT_ID_GELIN.apps.googleusercontent.com';

GoogleSignin.configure({
  webClientId: GOOGLE_WEB_CLIENT_ID,
  offlineAccess: false,
});

export default function LoginScreen() {
  const navigation = useNavigation<any>();
  const [loading, setLoading] = useState<'google' | 'facebook' | null>(null);

  async function handleGoogleLogin() {
    try {
      setLoading('google');
      await GoogleSignin.hasPlayServices();
      await GoogleSignin.signIn();
      const tokens = await GoogleSignin.getTokens();
      const idToken = tokens.idToken;

      if (!idToken) {
        throw new Error('Google idToken alınamadı');
      }

      const response = await googleSocialLogin(idToken);
      await SecureStore.setItemAsync('auth_token', response.token);
      await SecureStore.setItemAsync('user_data', JSON.stringify(response.user));

      if (response.profile_incomplete) {
        navigation.navigate('CompleteProfile', {
          token: response.token,
          missingFields: response.missing_fields,
        });
      } else {
        navigation.replace('Home');
      }
    } catch (error: any) {
      if (error.code === statusCodes.SIGN_IN_CANCELLED) {
        // Kullanıcı iptal etti
      } else if (error.code === statusCodes.IN_PROGRESS) {
        // Zaten devam ediyor
      } else {
        Alert.alert('Hata', error.message ?? 'Google ile giriş yapılamadı');
      }
    } finally {
      setLoading(null);
    }
  }

  async function handleFacebookLogin() {
    try {
      setLoading('facebook');

      const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);

      if (result.isCancelled) {
        return;
      }

      const tokenData = await AccessToken.getCurrentAccessToken();
      if (!tokenData?.accessToken) {
        throw new Error('Facebook access token alınamadı');
      }

      const response = await facebookSocialLogin(tokenData.accessToken.toString());
      await SecureStore.setItemAsync('auth_token', response.token);
      await SecureStore.setItemAsync('user_data', JSON.stringify(response.user));

      if (response.profile_incomplete) {
        navigation.navigate('CompleteProfile', {
          token: response.token,
          missingFields: response.missing_fields,
        });
      } else {
        navigation.replace('Home');
      }
    } catch (error: any) {
      Alert.alert('Hata', error.message ?? 'Facebook ile giriş yapılamadı');
    } finally {
      setLoading(null);
    }
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Astro Bilge</Text>
      <Text style={styles.subtitle}>Hesabınıza giriş yapın</Text>

      {/* Google */}
      <TouchableOpacity
        style={[styles.button, styles.googleButton]}
        onPress={handleGoogleLogin}
        disabled={loading !== null}
      >
        {loading === 'google' ? (
          <ActivityIndicator color="#333" />
        ) : (
          <Text style={styles.googleButtonText}>Google ile Giriş Yap</Text>
        )}
      </TouchableOpacity>

      {/* Facebook */}
      <TouchableOpacity
        style={[styles.button, styles.facebookButton]}
        onPress={handleFacebookLogin}
        disabled={loading !== null}
      >
        {loading === 'facebook' ? (
          <ActivityIndicator color="#fff" />
        ) : (
          <Text style={styles.facebookButtonText}>Facebook ile Giriş Yap</Text>
        )}
      </TouchableOpacity>

      {/* Normal giriş */}
      <TouchableOpacity onPress={() => navigation.navigate('EmailLogin')}>
        <Text style={styles.emailLink}>E-posta ile giriş yap</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, backgroundColor: '#fff' },
  title: { fontSize: 28, fontWeight: 'bold', color: '#136F63', marginBottom: 8 },
  subtitle: { fontSize: 16, color: '#666', marginBottom: 40 },
  button: { width: '100%', height: 52, borderRadius: 12, justifyContent: 'center', alignItems: 'center', marginBottom: 12 },
  googleButton: { backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd' },
  googleButtonText: { color: '#333', fontWeight: '600', fontSize: 16 },
  facebookButton: { backgroundColor: '#1877F2' },
  facebookButtonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
  emailLink: { marginTop: 20, color: '#136F63', textDecorationLine: 'underline' },
});
```

---

## Profil Tamamlama Ekranı

### `src/screens/CompleteProfileScreen.tsx`

```tsx
import React, { useState } from 'react';
import {
  View, Text, TextInput, TouchableOpacity,
  StyleSheet, Alert, ScrollView, ActivityIndicator,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { useNavigation, useRoute } from '@react-navigation/native';
import { completeProfile } from '../api/auth';

export default function CompleteProfileScreen() {
  const navigation = useNavigation<any>();
  const route = useRoute<any>();
  const { token, missingFields } = route.params as {
    token: string;
    missingFields: string[];
  };

  const [birthDate, setBirthDate]       = useState('');  // YYYY-MM-DD
  const [birthTime, setBirthTime]       = useState('');  // HH:MM
  const [gender, setGender]             = useState<'male' | 'female' | ''>('');
  const [phone, setPhone]               = useState('');
  const [cityQuery, setCityQuery]       = useState('');
  const [cityId, setCityId]             = useState<number | null>(null);
  const [cityResults, setCityResults]   = useState<any[]>([]);
  const [saving, setSaving]             = useState(false);

  const BASE_URL = 'https://siteniz.com/api';

  async function searchCities(q: string) {
    if (q.length < 2) { setCityResults([]); return; }
    try {
      const res = await fetch(`${BASE_URL}/cities/search?q=${encodeURIComponent(q)}`, {
        headers: { Accept: 'application/json' },
      });
      if (res.ok) setCityResults(await res.json());
    } catch { setCityResults([]); }
  }

  async function handleSave() {
    if (!birthDate) { Alert.alert('Uyarı', 'Doğum tarihi zorunludur.'); return; }
    if (!cityId)    { Alert.alert('Uyarı', 'Doğum yeri listeden seçilmelidir.'); return; }
    if (!gender)    { Alert.alert('Uyarı', 'Cinsiyet seçimi zorunludur.'); return; }

    setSaving(true);
    try {
      await completeProfile(token, {
        birth_date: birthDate,
        birth_place_id: cityId,
        gender,
        birth_time: birthTime || undefined,
        phone: phone || undefined,
      });

      // Token zaten SecureStore'da — doğrudan ana sayfaya geç
      navigation.replace('Home');
    } catch (e: any) {
      Alert.alert('Hata', e.message);
    } finally {
      setSaving(false);
    }
  }

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <Text style={styles.title}>Profil Bilgilerini Tamamla</Text>
      <Text style={styles.subtitle}>
        Astroloji haritanızın hesaplanabilmesi için aşağıdaki bilgiler gereklidir.
      </Text>

      {/* Cinsiyet */}
      <Text style={styles.label}>Cinsiyet *</Text>
      <View style={styles.genderRow}>
        {(['male', 'female'] as const).map(g => (
          <TouchableOpacity
            key={g}
            style={[styles.genderBtn, gender === g && styles.genderBtnActive]}
            onPress={() => setGender(g)}
          >
            <Text style={gender === g ? styles.genderBtnTextActive : styles.genderBtnText}>
              {g === 'male' ? 'Erkek' : 'Kadın'}
            </Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Doğum Tarihi */}
      <Text style={styles.label}>Doğum Tarihi * (YYYY-AA-GG)</Text>
      <TextInput
        style={styles.input}
        placeholder="1990-05-15"
        value={birthDate}
        onChangeText={setBirthDate}
        keyboardType="numbers-and-punctuation"
        maxLength={10}
      />

      {/* Doğum Saati */}
      <Text style={styles.label}>Doğum Saati (SS:DD, opsiyonel)</Text>
      <TextInput
        style={styles.input}
        placeholder="14:30"
        value={birthTime}
        onChangeText={setBirthTime}
        keyboardType="numbers-and-punctuation"
        maxLength={5}
      />

      {/* Telefon */}
      <Text style={styles.label}>Telefon (opsiyonel)</Text>
      <TextInput
        style={styles.input}
        placeholder="05551234567"
        value={phone}
        onChangeText={setPhone}
        keyboardType="phone-pad"
        maxLength={15}
      />

      {/* Doğum Yeri */}
      <Text style={styles.label}>Doğum Yeri *</Text>
      <TextInput
        style={styles.input}
        placeholder="Şehir adı yazın..."
        value={cityQuery}
        onChangeText={q => { setCityQuery(q); setCityId(null); searchCities(q); }}
      />
      {cityResults.map(city => (
        <TouchableOpacity
          key={city.id}
          style={styles.cityResult}
          onPress={() => {
            setCityId(city.id);
            setCityQuery([city.name, city.admin1_name, city.country_code].filter(Boolean).join(', '));
            setCityResults([]);
          }}
        >
          <Text>{[city.name, city.admin1_name, city.country_code].filter(Boolean).join(', ')}</Text>
        </TouchableOpacity>
      ))}

      <TouchableOpacity style={styles.saveBtn} onPress={handleSave} disabled={saving}>
        {saving ? <ActivityIndicator color="#fff" /> : <Text style={styles.saveBtnText}>Kaydet ve Devam Et</Text>}
      </TouchableOpacity>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: { padding: 24, backgroundColor: '#fff' },
  title: { fontSize: 22, fontWeight: 'bold', color: '#136F63', marginBottom: 8 },
  subtitle: { fontSize: 14, color: '#666', marginBottom: 24 },
  label: { fontSize: 14, fontWeight: '600', color: '#333', marginBottom: 6, marginTop: 12 },
  input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 10, padding: 12, fontSize: 16 },
  genderRow: { flexDirection: 'row', gap: 12, marginBottom: 4 },
  genderBtn: { flex: 1, padding: 12, borderRadius: 10, borderWidth: 1, borderColor: '#ddd', alignItems: 'center' },
  genderBtnActive: { backgroundColor: '#136F63', borderColor: '#136F63' },
  genderBtnText: { color: '#333', fontWeight: '600' },
  genderBtnTextActive: { color: '#fff', fontWeight: '600' },
  cityResult: { padding: 12, borderBottomWidth: 1, borderBottomColor: '#eee' },
  saveBtn: { backgroundColor: '#136F63', padding: 16, borderRadius: 12, alignItems: 'center', marginTop: 32 },
  saveBtnText: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
});
```

---

## Sıklıkla Yapılan Hatalar

| Hata | Nedeni | Çözümü |
|------|--------|--------|
| `DEVELOPER_ERROR` (Android Google) | SHA-1 uyuşmazlığı | Doğru keystore SHA-1'i Google Console'a ekle |
| `audience mismatch` (Google API) | Yanlış `webClientId` | `configure()` içinde **Web** Client ID kullan, Android/iOS değil |
| Facebook `Invalid OAuth access token` | App ID / Secret yanlış | Admin panelindeki `/admin/social-auth-settings` anahtarlarını kontrol et |
| `Network request failed` | Yanlış BASE_URL | `https://` kullandığından emin ol; geliştirmede ngrok/tunnel kullan |
| `401 Unauthorized` Google | `id_token` süresi dolmuş | SDK'dan her girişte yeni token al; cache'leme yapma |
| Profile tamamlama 422 | `birth_place_id` listeden seçilmedi | Autocomplete'den seçim yapıldığından emin ol |

---

## EAS Build ile Build Alma

```bash
# İlk kurulum
npm install -g eas-cli
eas login
eas build:configure

# Development build (Expo Go yerine bu kullanılır)
eas build --profile development --platform all

# Production
eas build --profile production --platform all
```

**`eas.json` örneği:**
```json
{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "production": {}
  }
}
```

---

## Checklist

### Google
- [ ] Google Cloud Console'da proje oluşturuldu
- [ ] Web, Android ve iOS için 3 ayrı Client ID oluşturuldu
- [ ] **Web Client ID** admin paneline girildi (`/admin/social-auth-settings`)
- [ ] Callback URL eklendi: `https://siteniz.com/auth/google/callback`
- [ ] Android SHA-1 parmak izi eklendi (debug + production)
- [ ] `google-services.json` (Android) ve `GoogleService-Info.plist` (iOS) indirilip proje köküne eklendi
- [ ] `app.json`'da `googleServicesFile` yolları doğru

### Facebook
- [ ] Facebook Geliştiriciler'de uygulama oluşturuldu
- [ ] App ID ve App Secret admin paneline girildi
- [ ] Android Key Hash eklendi
- [ ] iOS Bundle ID eklendi
- [ ] OAuth callback URL eklendi: `https://siteniz.com/auth/facebook/callback`
- [ ] Veri Silme callback URL eklendi: `https://siteniz.com/auth/facebook/data-deletion`
- [ ] `app.json`'da `appID` ve `clientToken` doğru

### Genel
- [ ] EAS Build ile build alındı (native modüller Expo Go'da çalışmaz)
- [ ] `BASE_URL` production adresini gösteriyor
- [ ] Token `expo-secure-store` ile saklanıyor (AsyncStorage değil)
- [ ] `profile_incomplete: true` gelince tamamlama ekranı açılıyor
- [ ] Şehir arama autocomplete çalışıyor (`/api/cities/search`)
