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
Maintainers
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-svgiOS Kurulumu
1. Pod kurulumu
cd ios && pod install2. 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_IMAGESkullanır;READ_EXTERNAL_STORAGEise Android 12 ve altı içinmaxSdkVersion="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 (nulldö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 | nullProvider dışında kullanıldığında
nulldöner.
Fotoğraf ve Kamera İzinleri
İzinler uygulama açılışında değil, kullanıcı "Fotoğraf Ekle" düğmesine bastığında istenir.
Akış
- Kullanıcı yorum formundaki "Fotoğraf Ekle" düğmesine basar.
- iOS'ta
ActionSheetIOS, Android'de bir bottom sheet açılır. - Kullanıcı "Galeriden Seç" veya "Kamerayla Çek" seçeneğini seçer.
react-native-image-pickerkütüphanesi işletim sisteminin izin iletişim kutusunu tetikler.- 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';