npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-crninja

v1.0.0

Published

E-ticaret uygulamalarında ürün yorumlarını, puanlarını ve sorularını görüntülemek için modüler bir React Native SDK

Readme

react-native-crninja

CRNinja ürün yorum ve soru-cevap altyapısını React Native uygulamalarına entegre etmek için resmi SDK.

Hazır bileşenler ve hook'lar aracılığıyla; yorum listeleme, yorum yazma, soru-cevap, ürün puan özeti ve galeri/kamera ile fotoğraf ekleme özelliklerini dakikalar içinde uygulamanıza ekleyebilirsiniz.


İçindekiler


Gereksinimler

| Bağımlılık | Minimum Sürüm | |------------|--------------| | React | 18.0.0 | | React Native | 0.73.0 | | react-native-image-picker | 7.0.0 | | react-native-svg | 15.0.0 |


Kurulum

npm install react-native-crninja react-native-image-picker react-native-svg

iOS Kurulumu

1. Pod kurulumu

cd ios && pod install

2. Kamera ve galeri izinleri — Info.plist

ios/[ProjeAdi]/Info.plist dosyasını açın. Aşağıdaki <key> ve <string> çiftlerini ana <dict> bloğunun içine, </dict> kapanış etiketinden hemen önce ekleyin:

<key>NSPhotoLibraryUsageDescription</key>
<string>Yorum için fotoğraf seçmek istiyoruz.</string>
<key>NSCameraUsageDescription</key>
<string>Yorum için fotoğraf çekmek istiyoruz.</string>

Önemli:

  • <key> ve <string> çiftleri her zaman ana <dict> bloğunun doğrudan alt elemanı olmalıdır.
  • <plist> etiketinin dışına veya iç içe geçmiş başka bir <dict> içine konulmamalıdır.
  • Bu anahtarlar olmadan uygulama gerçek cihazda anında kapanır (crash). App Store incelemesinde de reddedilir.

Android Kurulumu

Kamera ve galeri izinleri — AndroidManifest.xml

android/app/src/main/AndroidManifest.xml dosyasını açın. İzin satırlarını <manifest> etiketinin altına, <application> etiketinin dışına ve öncesine ekleyin:

<!-- Kamera ve galeri izinleri — <application> etiketinden ÖNCE eklenmelidir -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Android 12 (API 32) ve altı için gereklidir -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

Önemli:

  • <uses-permission> etiketleri her zaman <manifest> bloğunun doğrudan alt elemanı olmalıdır.
  • <application> veya <activity> içine konulmamalıdır.
  • Android 13+ (API 33+) cihazlar READ_MEDIA_IMAGES kullanır; READ_EXTERNAL_STORAGE ise Android 12 ve altı için maxSdkVersion="32" ile sınırlandırılmıştır.

SDK Başlatma

Bileşen veya hook kullanmadan önce, uygulamanızın giriş noktasında (App.tsx veya index.js) SDK'yı bir kez başlatın:

import { CRNinja } from 'react-native-crninja';

CRNinja.initialize('CCID_DEGERINIZ');

Bileşen API

Hazır bileşenler; veri çekme, durum yönetimi, filtreleme, arama ve sıralama işlemlerini içten yönetir. Kullanımı sadece birkaç satır kod gerektirir.


Comments

Ürün yorumlarını listeleyen tam özellikli bileşen. Puan özeti, yıldız dağılımı, AI özet metni, yıldız/beden/fotoğraf filtreleri, arama ve sıralama içerir.

import { Comments } from 'react-native-crninja';

<Comments productCode="URUN_KODU" />

Props

| Prop | Tür | Varsayılan | Açıklama | |------|-----|-----------|----------| | productCode | string | — | Ürün kodu (zorunlu) | | pageSize | number | 10 | Sayfa başına yorum sayısı | | renderItem | (item: Comment) => React.ReactNode | — | Özel yorum satırı render fonksiyonu | | onError | (err: Error) => void | — | Hata geri bildirimi | | style | StyleProp<ViewStyle> | — | Dış kapsayıcı stili | | testID | string | — | Test tanımlayıcısı | | scrollEnabled | boolean | true | ScrollView içine gömüldüğünde false verin |

Temel kullanım

import { Comments } from 'react-native-crninja';

export function ProductDetailScreen() {
  return (
    <Comments
      productCode="ABC123"
      pageSize={10}
      onError={(err) => console.error(err)}
    />
  );
}

ScrollView içine gömmek

Bileşen kendi içinde FlatList kullanır. Dış bir ScrollView içine yerleştirirken scrollEnabled={false} verin; aksi hâlde iç içe kaydırma çakışması oluşur:

<ScrollView>
  {/* Diğer içerikler */}
  <Comments
    productCode="ABC123"
    scrollEnabled={false}
  />
</ScrollView>

Özel yorum satırı render etmek

<Comments
  productCode="ABC123"
  renderItem={(comment) => (
    <View style={styles.customCard}>
      <Text style={styles.author}>{comment.userFullName}</Text>
      <Text>{comment.comment}</Text>
    </View>
  )}
/>

Questions

Ürün soru-cevaplarını listeleyen bileşen. Soru sayısı, arama ve sıralama içerir.

import { Questions } from 'react-native-crninja';

<Questions productCode="URUN_KODU" />

Props

| Prop | Tür | Varsayılan | Açıklama | |------|-----|-----------|----------| | productCode | string | — | Ürün kodu (zorunlu) | | pageSize | number | 10 | Sayfa başına soru sayısı | | onError | (err: Error) => void | — | Hata geri bildirimi | | style | StyleProp<ViewStyle> | — | Dış kapsayıcı stili | | testID | string | — | Test tanımlayıcısı | | scrollEnabled | boolean | true | ScrollView içine gömüldüğünde false verin |

Örnek

import { Questions } from 'react-native-crninja';

export function ProductDetailScreen() {
  return (
    <Questions
      productCode="ABC123"
      pageSize={5}
      scrollEnabled={false}
    />
  );
}

MiniReview

Ürün listesi kartlarında kullanılan özet bileşeni. Yıldız puanı, ortalama, yorum sayısı, soru sayısı, favori sayısı ve beden tavsiyesini gösterir. Her alan appStyles yapılandırmasına göre koşullu olarak görüntülenir.

import { MiniReview } from 'react-native-crninja';

<MiniReview productCode="URUN_KODU" />

Props

| Prop | Tür | Varsayılan | Açıklama | |------|-----|-----------|----------| | productCode | string | — | Ürün kodu (zorunlu) | | style | StyleProp<ViewStyle> | — | Dış kapsayıcı stili | | testID | string | — | Test tanımlayıcısı |

Örnek — Ürün listesi kartında

import { MiniReview } from 'react-native-crninja';

function ProductCard({ product }: { product: Product }) {
  return (
    <View style={styles.card}>
      <Image source={{ uri: product.imageUrl }} style={styles.image} />
      <Text style={styles.name}>{product.name}</Text>
      <Text style={styles.price}>{product.price} TL</Text>
      <MiniReview productCode={product.code} />
    </View>
  );
}

Not: MiniReview, veri yokken veya yüklenirken hiçbir şey render etmez (null döner). Kartın düzeni bozulmaz.


ProductRatingSummary

useProductRatings hook'uyla birlikte listeleme sayfalarında kullanılmak üzere tasarlanmış saf (pure) bileşen. Puan verisini prop olarak alır, kendi API isteği yapmaz.

import { ProductRatingSummary } from 'react-native-crninja';

<ProductRatingSummary reviewData={review} appStyles={appStyles} />

Props

| Prop | Tür | Zorunlu | Açıklama | |------|-----|---------|----------| | reviewData | ListingPageReview | Evet | useProductRatings'den gelen özet veri | | appStyles | AppStyles \| null | Hayır | Stil yapılandırması | | style | StyleProp<ViewStyle> | Hayır | Dış kapsayıcı stili | | testID | string | Hayır | Test tanımlayıcısı |

Örnek — useProductRatings ile birlikte

import { useProductRatings, ProductRatingSummary } from 'react-native-crninja';

function ProductGrid({ products }: { products: Product[] }) {
  const productCodes = products.map(p => p.code);
  const { reviews, appStyles, isLoading } = useProductRatings(productCodes);

  if (isLoading) return <ActivityIndicator />;

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => {
        const review = reviews.find(r => r.feedSKU === item.code);
        return (
          <View style={styles.card}>
            <Text>{item.name}</Text>
            {review && (
              <ProductRatingSummary
                reviewData={review}
                appStyles={appStyles}
              />
            )}
          </View>
        );
      }}
    />
  );
}

NewCommentForm

Yorum yazma formu. Modal olarak görüntülenir; yıldız puanı, yorum metni, ad-soyad, fotoğraf ekleme ve yayınlama izni onayı içerir. Doğrulama ve API gönderimi dahilidir.

import { NewCommentForm } from 'react-native-crninja';

<NewCommentForm
  productCode="URUN_KODU"
  visible={formVisible}
  onClose={() => setFormVisible(false)}
/>

Props

| Prop | Tür | Varsayılan | Açıklama | |------|-----|-----------|----------| | productCode | string | — | Ürün kodu (zorunlu) | | visible | boolean | — | Modalın açık/kapalı durumu (zorunlu) | | onClose | () => void | — | Modal kapatma geri bildirimi (zorunlu) | | onSuccess | () => void | — | Başarılı gönderim geri bildirimi | | onError | (err: Error) => void | — | Hata geri bildirimi | | style | StyleProp<ViewStyle> | — | Dış kapsayıcı stili | | testID | string | — | Test tanımlayıcısı |

Örnek

import { useState } from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { NewCommentForm } from 'react-native-crninja';

export function ProductDetailScreen() {
  const [formVisible, setFormVisible] = useState(false);

  return (
    <>
      <TouchableOpacity onPress={() => setFormVisible(true)}>
        <Text>Yorum Yaz</Text>
      </TouchableOpacity>

      <NewCommentForm
        productCode="ABC123"
        visible={formVisible}
        onClose={() => setFormVisible(false)}
        onSuccess={() => {
          setFormVisible(false);
          // Yorum listesini yenilemek için refresh() çağırabilirsiniz
        }}
        onError={(err) => console.error(err)}
      />
    </>
  );
}

Fotoğraf izni: Kullanıcı form içindeki "Fotoğraf Ekle" düğmesine bastığında işletim sistemi izin iletişim kutusunu gösterir. Uygulama açılışında izin istenmez.


NewQuestionForm

Soru sorma formu. Modal olarak görüntülenir; soru metni, ad-soyad ve yayınlama izni onayı içerir.

import { NewQuestionForm } from 'react-native-crninja';

<NewQuestionForm
  productCode="URUN_KODU"
  visible={questionFormVisible}
  onClose={() => setQuestionFormVisible(false)}
/>

Props

| Prop | Tür | Varsayılan | Açıklama | |------|-----|-----------|----------| | productCode | string | — | Ürün kodu (zorunlu) | | visible | boolean | — | Modalın açık/kapalı durumu (zorunlu) | | onClose | () => void | — | Modal kapatma geri bildirimi (zorunlu) | | onSuccess | () => void | — | Başarılı gönderim geri bildirimi | | onError | (err: Error) => void | — | Hata geri bildirimi | | style | StyleProp<ViewStyle> | — | Dış kapsayıcı stili | | testID | string | — | Test tanımlayıcısı |

Örnek

import { useState } from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { NewQuestionForm } from 'react-native-crninja';

export function ProductDetailScreen() {
  const [questionVisible, setQuestionVisible] = useState(false);

  return (
    <>
      <TouchableOpacity onPress={() => setQuestionVisible(true)}>
        <Text>Soru Sor</Text>
      </TouchableOpacity>

      <NewQuestionForm
        productCode="ABC123"
        visible={questionVisible}
        onClose={() => setQuestionVisible(false)}
        onSuccess={() => setQuestionVisible(false)}
      />
    </>
  );
}

Hook API

Hook'lar, veri ve iş mantığını sunum katmanından ayırır. Hazır bileşenler yetersiz kaldığında tamamen özel bir arayüz oluşturmanıza olanak tanır.


useComments

Yorum listeleme mantığını yönetir: veri çekme, sayfalama, çoklu yıldız/beden filtreleri, fotoğraf filtresi, arama ve sıralama.

const result = useComments(productCode, options);

Parametreler

| Parametre | Tür | Açıklama | |-----------|-----|----------| | productCode | string | Ürün kodu (zorunlu) | | options.pageSize | number | Sayfa başına yorum sayısı. Varsayılan: 10 | | options.onError | (err: Error) => void | Hata geri bildirimi |

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | comments | Comment[] | Yüklenen yorumlar | | isLoading | boolean | Yükleme durumu | | isError | boolean | Hata durumu | | error | Error \| null | Hata nesnesi | | loadMore | () => void | Sonraki sayfayı yükler | | hasMore | boolean | Daha fazla yorum var mı | | summary | CommentSummary \| null | Puan özeti ve meta veriler | | appStyles | AppStyles \| null | API'den gelen stil yapılandırması | | activeStarFilters | number[] | Seçili yıldız filtreleri (çoklu seçim) | | setActiveStarFilters | (stars: number[]) => void | Yıldız filtrelerini günceller | | activeSizeFilters | string[] | Seçili beden filtreleri (çoklu seçim) | | setActiveSizeFilters | (sizes: string[]) => void | Beden filtrelerini günceller | | onlyMedia | boolean | Sadece fotoğraflı yorumlar filtresi | | setOnlyMedia | (enabled: boolean) => void | Fotoğraf filtresini açar/kapar | | searchQuery | string | Mevcut arama metni | | setSearchQuery | (query: string) => void | Arama metnini günceller | | sortOrder | 'az' \| 'za' | Sıralama yönü ('za' = en yeni önce) | | setSortOrder | (order: 'az' \| 'za') => void | Sıralamayı değiştirir | | refresh | () => void | Listeyi baştan yükler |

Not: Herhangi bir filtre, arama veya sıralama değiştiğinde liste otomatik olarak sıfırlanır ve baştan yüklenir.

CommentSummary Alanları

| Alan | Tür | Açıklama | |------|-----|----------| | commentCount | number | Toplam yorum sayısı | | mediaCommentCount | number | Fotoğraflı yorum sayısı | | rateAVG | string | Puan ortalaması | | rateAndCounts | RateAndCount[] | Yıldız bazında dağılım | | sizesAndCounts | SizeAndCount[] | Beden bazında dağılım | | reviewCount | number | İnceleme sayısı | | summarize | string | AI tarafından oluşturulan özet metin | | favoriteCount | number | Favoriye eklenme sayısı | | sizeReco | string | Beden tavsiyesi metni | | newCommentActive | boolean | Yorum yazma özelliği etkin mi | | commentSortActive | boolean | Sıralama etkin mi | | commentSearchActive | boolean | Arama etkin mi |

Örnek — Özel yorum listesi

import { useComments } from 'react-native-crninja';
import { FlatList, Text, View, TouchableOpacity, ActivityIndicator } from 'react-native';

export function CustomCommentList({ productCode }: { productCode: string }) {
  const {
    comments,
    isLoading,
    hasMore,
    loadMore,
    summary,
    activeStarFilters,
    setActiveStarFilters,
    searchQuery,
    setSearchQuery,
    refresh,
  } = useComments(productCode, { pageSize: 5 });

  return (
    <View>
      {/* Özet */}
      {summary && (
        <View>
          <Text>Ortalama: {summary.rateAVG}</Text>
          <Text>Toplam: {summary.commentCount} yorum</Text>
        </View>
      )}

      {/* Arama */}
      <TextInput
        value={searchQuery}
        onChangeText={setSearchQuery}
        placeholder="Yorumlarda ara..."
      />

      {/* Yıldız filtreleri */}
      <View style={{ flexDirection: 'row' }}>
        {[5, 4, 3, 2, 1].map(star => (
          <TouchableOpacity
            key={star}
            onPress={() =>
              setActiveStarFilters(
                activeStarFilters.includes(star)
                  ? activeStarFilters.filter(s => s !== star)
                  : [...activeStarFilters, star]
              )
            }
          >
            <Text>{star} Yıldız</Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Liste */}
      <FlatList
        data={comments}
        keyExtractor={item => String(item.id)}
        renderItem={({ item }) => (
          <View>
            <Text>{item.userFullName}</Text>
            <Text>{item.comment}</Text>
          </View>
        )}
        onEndReached={loadMore}
        onEndReachedThreshold={0.3}
        ListFooterComponent={isLoading ? <ActivityIndicator /> : null}
        refreshing={isLoading && comments.length === 0}
        onRefresh={refresh}
      />
    </View>
  );
}

useQuestions

Soru-cevap listeleme mantığını yönetir: veri çekme, sayfalama, arama ve sıralama.

const result = useQuestions(productCode, options);

Parametreler

| Parametre | Tür | Açıklama | |-----------|-----|----------| | productCode | string | Ürün kodu (zorunlu) | | options.pageSize | number | Sayfa başına soru sayısı. Varsayılan: 10 | | options.onError | (err: Error) => void | Hata geri bildirimi |

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | questions | Question[] | Yüklenen sorular | | isLoading | boolean | Yükleme durumu | | isError | boolean | Hata durumu | | error | Error \| null | Hata nesnesi | | loadMore | () => void | Sonraki sayfayı yükler | | hasMore | boolean | Daha fazla soru var mı | | questionsCount | number | Toplam soru sayısı | | newQuestionActive | boolean | Soru sorma özelliği etkin mi | | appStyles | AppStyles \| null | API'den gelen stil yapılandırması | | searchQuery | string | Mevcut arama metni | | setSearchQuery | (query: string) => void | Arama metnini günceller | | sortOrder | 'az' \| 'za' | Sıralama yönü | | setSortOrder | (order: 'az' \| 'za') => void | Sıralamayı değiştirir | | refresh | () => void | Listeyi baştan yükler |

Örnek

import { useQuestions } from 'react-native-crninja';

export function QuestionSection({ productCode }: { productCode: string }) {
  const { questions, isLoading, questionsCount, loadMore, hasMore } =
    useQuestions(productCode, { pageSize: 5 });

  return (
    <View>
      <Text>{questionsCount} Soru</Text>
      {questions.map(q => (
        <View key={q.id}>
          <Text>{q.question}</Text>
          {q.answer ? <Text>Yanıt: {q.answer}</Text> : null}
        </View>
      ))}
      {hasMore && !isLoading && (
        <TouchableOpacity onPress={loadMore}>
          <Text>Daha Fazla</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}

useProductRatings

Listeleme/kategori sayfaları için birden fazla ürünün puan özetini tek API isteğiyle getirir.

const result = useProductRatings(productCodes, options);

Parametreler

| Parametre | Tür | Açıklama | |-----------|-----|----------| | productCodes | string[] | Puan özeti istenen ürün kodları (zorunlu) | | options.onError | (err: Error) => void | Hata geri bildirimi |

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | reviews | ListingPageReview[] | Ürün başına puan özeti. feedSKU alanıyla ürünle eşleştirin. | | isLoading | boolean | Yükleme durumu | | isError | boolean | Hata durumu | | error | Error \| null | Hata nesnesi | | appStyles | AppStyles \| null | Stil yapılandırması | | refresh | () => void | Verileri yeniden yükler |

Örnek

import { useProductRatings, ProductRatingSummary } from 'react-native-crninja';

const PRODUCTS = [
  { code: 'ABC', name: 'Ürün 1' },
  { code: 'DEF', name: 'Ürün 2' },
  { code: 'GHI', name: 'Ürün 3' },
];

export function ProductListScreen() {
  const productCodes = PRODUCTS.map(p => p.code);
  const { reviews, appStyles, isLoading } = useProductRatings(productCodes);

  return (
    <FlatList
      data={PRODUCTS}
      keyExtractor={item => item.code}
      renderItem={({ item }) => {
        const review = reviews.find(r => r.feedSKU === item.code);
        return (
          <View style={styles.card}>
            <Text>{item.name}</Text>
            {review && (
              <ProductRatingSummary
                reviewData={review}
                appStyles={appStyles}
              />
            )}
          </View>
        );
      }}
    />
  );
}

useNewCommentForm

Yorum yazma formunun iş mantığını yönetir. Doğrulama, fotoğraf yükleme ve API gönderimini soyutlar; arayüz tamamen size bırakılır.

const result = useNewCommentForm(productCode, options);

Parametreler

| Parametre | Tür | Açıklama | |-----------|-----|----------| | productCode | string | Ürün kodu (zorunlu) | | options.onSuccess | () => void | Başarılı gönderim geri bildirimi | | options.onError | (err: Error) => void | Hata geri bildirimi |

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | rating | number | Seçili yıldız puanı (1–5). 0 = seçilmedi. | | setRating | (rating: number) => void | Puanı günceller | | userName | string | Kullanıcı adı-soyadı | | setUserName | (name: string) => void | Adı günceller | | commentText | string | Yorum metni | | setCommentText | (text: string) => void | Yorum metnini günceller | | consentChecked | boolean | Yayınlama izni onayı | | setConsentChecked | (checked: boolean) => void | Onay durumunu günceller | | showNameChecked | boolean | İsim gösterim izni | | setShowNameChecked | (checked: boolean) => void | İsim iznini günceller | | imagePicker | UseImagePickerReturn | Fotoğraf seçimi hook'u (bkz. useImagePicker) | | isSubmitting | boolean | Gönderim devam ediyor mu | | validationMsg | string | Kullanıcıya gösterilecek doğrulama veya hata mesajı | | submitResult | CommentSubmitResult \| null | API'den gelen başarı/hata sonucu | | submit | () => Promise<void> | Formu doğrular, fotoğrafları yükler ve gönderir | | resetForm | () => void | Tüm alanları ve sonuç durumunu sıfırlar |

Doğrulama kuralları

submit() çağrıldığında aşağıdaki koşullar sağlanmadığında validationMsg alanı hata mesajıyla güncellenir ve gönderim gerçekleşmez:

| Kural | Hata mesajı | |-------|-------------| | Puan seçilmemiş | Lütfen bir puan seçin. | | Ad-soyad boş | Lütfen adınızı ve soyadınızı girin. | | Yorum 2 karakterden kısa | Yorum en az 2 karakter olmalıdır. | | Yayınlama onayı verilmemiş | Yorumunuzun yayınlanmasına izin vermeniz gerekiyor. |

Örnek — Özel yorum formu

import { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Switch } from 'react-native';
import { useNewCommentForm } from 'react-native-crninja';

export function CustomCommentForm({ productCode, onDone }: Props) {
  const {
    rating, setRating,
    userName, setUserName,
    commentText, setCommentText,
    consentChecked, setConsentChecked,
    imagePicker,
    isSubmitting,
    validationMsg,
    submitResult,
    submit,
  } = useNewCommentForm(productCode, {
    onSuccess: onDone,
  });

  return (
    <View>
      {/* Yıldız seçimi */}
      <View style={{ flexDirection: 'row' }}>
        {[1, 2, 3, 4, 5].map(star => (
          <TouchableOpacity key={star} onPress={() => setRating(star)}>
            <Text style={{ color: rating >= star ? '#FFD700' : '#ccc' }}>★</Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Ad-soyad */}
      <TextInput
        value={userName}
        onChangeText={setUserName}
        placeholder="Adınız Soyadınız"
      />

      {/* Yorum metni */}
      <TextInput
        value={commentText}
        onChangeText={setCommentText}
        placeholder="Yorumunuzu yazın..."
        multiline
      />

      {/* Fotoğraf ekleme */}
      <TouchableOpacity onPress={imagePicker.openGallery}>
        <Text>Galeriden Fotoğraf Ekle ({imagePicker.selectedImages.length}/3)</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={imagePicker.openCamera}>
        <Text>Kamerayla Fotoğraf Çek</Text>
      </TouchableOpacity>

      {/* Yayınlama onayı */}
      <View style={{ flexDirection: 'row', alignItems: 'center' }}>
        <Switch value={consentChecked} onValueChange={setConsentChecked} />
        <Text>Yorumumun yayınlanmasına izin veriyorum.</Text>
      </View>

      {/* Hata mesajı */}
      {validationMsg ? <Text style={{ color: 'red' }}>{validationMsg}</Text> : null}

      {/* Başarı/hata sonucu */}
      {submitResult && (
        <Text style={{ color: submitResult.success ? 'green' : 'red' }}>
          {submitResult.message}
        </Text>
      )}

      {/* Gönder butonu */}
      <TouchableOpacity onPress={submit} disabled={isSubmitting}>
        <Text>{isSubmitting ? 'Gönderiliyor...' : 'Yorum Gönder'}</Text>
      </TouchableOpacity>
    </View>
  );
}

useNewQuestionForm

Soru sorma formunun iş mantığını yönetir.

const result = useNewQuestionForm(productCode, options);

Parametreler

| Parametre | Tür | Açıklama | |-----------|-----|----------| | productCode | string | Ürün kodu (zorunlu) | | options.onSuccess | () => void | Başarılı gönderim geri bildirimi | | options.onError | (err: Error) => void | Hata geri bildirimi |

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | userName | string | Kullanıcı adı-soyadı | | setUserName | (name: string) => void | Adı günceller | | questionText | string | Soru metni | | setQuestionText | (text: string) => void | Soru metnini günceller | | consentChecked | boolean | Yayınlama izni onayı | | setConsentChecked | (checked: boolean) => void | Onay durumunu günceller | | showNameChecked | boolean | İsim gösterim izni | | setShowNameChecked | (checked: boolean) => void | İsim iznini günceller | | isSubmitting | boolean | Gönderim devam ediyor mu | | validationMsg | string | Doğrulama veya hata mesajı | | submitResult | QuestionSubmitResult \| null | API'den gelen başarı/hata sonucu | | submit | () => Promise<void> | Formu doğrular ve gönderir | | resetForm | () => void | Tüm alanları sıfırlar |

Örnek

import { useNewQuestionForm } from 'react-native-crninja';

export function CustomQuestionForm({ productCode, onDone }: Props) {
  const {
    userName, setUserName,
    questionText, setQuestionText,
    consentChecked, setConsentChecked,
    isSubmitting,
    validationMsg,
    submit,
  } = useNewQuestionForm(productCode, { onSuccess: onDone });

  return (
    <View>
      <TextInput
        value={userName}
        onChangeText={setUserName}
        placeholder="Adınız Soyadınız"
      />
      <TextInput
        value={questionText}
        onChangeText={setQuestionText}
        placeholder="Sorunuzu yazın..."
        multiline
      />
      <Switch value={consentChecked} onValueChange={setConsentChecked} />
      {validationMsg ? <Text style={{ color: 'red' }}>{validationMsg}</Text> : null}
      <TouchableOpacity onPress={submit} disabled={isSubmitting}>
        <Text>{isSubmitting ? 'Gönderiliyor...' : 'Soruyu Gönder'}</Text>
      </TouchableOpacity>
    </View>
  );
}

useImagePicker

Galeri ve kamera erişimini soyutlar. useNewCommentForm tarafından içten kullanılır; ayrıca doğrudan da kullanılabilir.

const result = useImagePicker(options);

Dönüş Değerleri

| Alan | Tür | Açıklama | |------|-----|----------| | selectedImages | SelectedImage[] | Seçilen fotoğraflar | | openGallery | () => Promise<void> | Galeriyi açar | | openCamera | () => Promise<void> | Kamerayı açar | | removeImage | (uri: string) => void | Belirtilen fotoğrafı listeden kaldırır | | isLoading | boolean | Yükleme durumu | | error | ImagePickerError \| null | Hata nesnesi |

Fotoğraf limitleri

| Kaynak | Limit | |--------|-------| | Galeri | En fazla 3 fotoğraf (toplam galeri seçimi) | | Kamera | En fazla 1 fotoğraf |

Limit aşıldığında error.code === 'MAX_REACHED' ile hata döner; ek fotoğraf seçilemez.

SelectedImage Alanları

| Alan | Tür | Açıklama | |------|-----|----------| | uri | string | Dosya URI'si | | fileName | string | Dosya adı | | type | string | MIME türü (örn. image/jpeg) | | fileSize | number | Dosya boyutu (byte) | | source | 'gallery' \| 'camera' | Seçim kaynağı |

Örnek

import { useImagePicker } from 'react-native-crninja';
import { View, TouchableOpacity, Image, Text } from 'react-native';

export function PhotoPickerExample() {
  const { selectedImages, openGallery, openCamera, removeImage, error } =
    useImagePicker({ maxImages: 3 });

  return (
    <View>
      <TouchableOpacity onPress={openGallery}>
        <Text>Galeriden Seç</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={openCamera}>
        <Text>Kamerayla Çek</Text>
      </TouchableOpacity>

      {error && <Text style={{ color: 'red' }}>{error.message}</Text>}

      <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
        {selectedImages.map(img => (
          <View key={img.uri}>
            <Image source={{ uri: img.uri }} style={{ width: 80, height: 80 }} />
            <TouchableOpacity onPress={() => removeImage(img.uri)}>
              <Text>Kaldır</Text>
            </TouchableOpacity>
          </View>
        ))}
      </View>
    </View>
  );
}

useAppStyles

AppStylesProvider içindeyken API'den gelen stil yapılandırmasını döner. SDK'nın kendi bileşenlerinde içten kullanılır; özel bileşenlerinizde de yararlanabilirsiniz.

const appStyles = useAppStyles(); // AppStyles | null

Provider dışında kullanıldığında null döner.


Fotoğraf ve Kamera İzinleri

İzinler uygulama açılışında değil, kullanıcı "Fotoğraf Ekle" düğmesine bastığında istenir.

Akış

  1. Kullanıcı yorum formundaki "Fotoğraf Ekle" düğmesine basar.
  2. iOS'ta ActionSheetIOS, Android'de bir bottom sheet açılır.
  3. Kullanıcı "Galeriden Seç" veya "Kamerayla Çek" seçeneğini seçer.
  4. react-native-image-picker kütüphanesi işletim sisteminin izin iletişim kutusunu tetikler.
  5. Kullanıcı izin verirse galeri veya kamera açılır.

İzin verilmezse ne olur?

İzin reddedildiğinde galeri veya kamera açılmaz; useImagePicker içindeki error alanı güncellenir. Form kullanılabilir olmaya devam eder; fotoğrafsız yorum gönderilebilir.

İzinler eklenmezse ne olur?

| Platform | Sonuç | |----------|-------| | iOS (gerçek cihaz) | Uygulama anında kapanır (crash). App Store incelemesinde reddedilir. | | Android (gerçek cihaz) | Özellik çalışmaz; SecurityException ile uygulama çökebilir. | | iOS Simülatör | Simülatör tam izin denetimi uygulamadığından çökmeyebilir. Gerçek cihazda mutlaka test edin. | | Android Emülatör | Emülatör izin katmanlarını tam taklit etmediğinden sorunsuz görünebilir. Gerçek cihazda mutlaka test edin. |


TypeScript Türleri

SDK'nın dışa aktardığı başlıca türler:

import type {
  // Yorum türleri
  Comment,
  CommentMedia,
  RateAndCount,
  SizeAndCount,
  Rate,
  Flag,
  ListingPageReview,
  GetListingPageReviewsResponse,

  // Soru türleri
  Question,

  // Stil türleri
  AppStyles,

  // Hata türleri
  ApiError,

  // Hook seçenek ve dönüş türleri
  UseCommentsReturn,
  UseCommentsOptions,
  CommentSummary,
  UseQuestionsReturn,
  UseQuestionsOptions,
  UseProductRatingsReturn,
  UseProductRatingsOptions,
  UseNewCommentFormReturn,
  UseNewCommentFormOptions,
  CommentSubmitResult,
  UseNewQuestionFormReturn,
  UseNewQuestionFormOptions,
  QuestionSubmitResult,
  UseImagePickerReturn,
  SelectedImage,
  ImagePickerError,

  // Bileşen prop türleri
  ProductRatingSummaryProps,
} from 'react-native-crninja';