This commit is contained in:
2026-07-08 22:07:59 +02:00
parent 0b2305a3ad
commit 9be9b2987b
10 changed files with 349 additions and 268 deletions

View File

@@ -19,13 +19,13 @@ import { PlantRecognitionService } from '../services/plantRecognitionService';
import { IdentificationResult } from '../types';
import { ResultCard } from '../components/ResultCard';
import { backendApiClient, isInsufficientCreditsError, isNetworkError, isTimeoutError } from '../services/backend/backendApiClient';
import { isBackendApiError } from '../services/backend/contracts';
import { createIdempotencyKey } from '../utils/idempotency';
import { AuthService } from '../services/authService';
import { consumeSharedImageUri, SHARE_INTENT_KEY } from '../utils/shareHandoff';
import { OutOfCreditsSheet } from '../components/OutOfCreditsSheet';
import { isBackendApiError } from '../services/backend/contracts';
import { createIdempotencyKey } from '../utils/idempotency';
import { AuthService } from '../services/authService';
import { consumeSharedImageUri, SHARE_INTENT_KEY } from '../utils/shareHandoff';
import { OutOfCreditsSheet } from '../components/OutOfCreditsSheet';
const DEMO_SCAN_LIMIT = 3;
const DEMO_SCAN_LIMIT = 1;
const getBillingCopy = (language: 'de' | 'en' | 'es') => {
if (language === 'de') {
@@ -51,8 +51,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => {
demoTitle: 'Rettungsplan bereit',
demoMessage: 'Wir haben mögliche Ursachen erkannt. Schalte die vollständige KI-Diagnose und deinen 7-Tage-Rettungsplan frei.',
demoNoCreditsTitle: 'Demo-Scans aufgebraucht',
demoNoCreditsMessage: 'Du hast deine 3 kostenlosen Demo-Scans auf diesem Gerät genutzt. Starte Pro, um weiter Pflanzen zu scannen.',
demoCreditsRemaining: (count: number) => `${count} Demo-Scans übrig`,
demoNoCreditsMessage: 'Du hast deinen kostenlosen Demo-Scan auf diesem Gerät genutzt. Starte Pro, um weiter Pflanzen zu scannen.',
demoCreditsRemaining: (count: number) => (count === 1 ? '1 Demo-Scan übrig' : `${count} Demo-Scans übrig`),
creditsRemaining: (count: number) => `${count} Scans übrig`,
appleCta: 'Mit Apple fortfahren',
emailCta: 'Mit E-Mail fortfahren',
@@ -83,8 +83,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => {
demoTitle: 'Plan de rescate listo',
demoMessage: 'Detectamos posibles causas. Desbloquea el diagnóstico completo con IA y tu plan de rescate de 7 días.',
demoNoCreditsTitle: 'Escaneos demo agotados',
demoNoCreditsMessage: 'Ya usaste tus 3 escaneos demo gratuitos en este dispositivo. Inicia Pro para seguir escaneando plantas.',
demoCreditsRemaining: (count: number) => `${count} escaneos demo restantes`,
demoNoCreditsMessage: 'Ya usaste tu escaneo demo gratuito en este dispositivo. Inicia Pro para seguir escaneando plantas.',
demoCreditsRemaining: (count: number) => (count === 1 ? '1 escaneo demo restante' : `${count} escaneos demo restantes`),
creditsRemaining: (count: number) => `${count} escaneos restantes`,
appleCta: 'Continuar con Apple',
emailCta: 'Continuar con email',
@@ -114,8 +114,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => {
demoTitle: 'Rescue plan ready',
demoMessage: 'We found possible causes. Unlock the full AI diagnosis and your 7-day rescue plan.',
demoNoCreditsTitle: 'Demo scans used',
demoNoCreditsMessage: 'You used your 3 free demo scans on this device. Start Pro to keep scanning plants.',
demoCreditsRemaining: (count: number) => `${count} demo scans left`,
demoNoCreditsMessage: 'You used your free demo scan on this device. Start Pro to keep scanning plants.',
demoCreditsRemaining: (count: number) => (count === 1 ? '1 demo scan left' : `${count} demo scans left`),
creditsRemaining: (count: number) => `${count} scans left`,
appleCta: 'Continue with Apple',
emailCta: 'Continue with email',
@@ -142,6 +142,7 @@ export default function ScannerScreen() {
setPendingPlant,
guestScanCount,
incrementGuestScanCount,
hasCompletedOnboarding,
} = useApp();
const colors = useColors(isDarkMode, colorPalette);
const router = useRouter();
@@ -160,7 +161,7 @@ export default function ScannerScreen() {
: params.sharedImageKey;
const hasActiveEntitlement = billingSummary?.entitlement?.plan === 'pro'
&& billingSummary?.entitlement?.status === 'active';
const isDemoMode = !session; // guests get limited AI demo scans; signed-in users burn real credits
const isDemoMode = !session; // guests get limited AI demo scans; signed-in users burn real credits
const availableCredits = billingSummary?.credits.available ?? 0;
const demoScansRemaining = Math.max(0, DEMO_SCAN_LIMIT - guestScanCount);
@@ -293,22 +294,22 @@ export default function ScannerScreen() {
}, 150);
try {
if (isDemoMode) {
posthog.capture('demo_scan_started', {
authenticated: Boolean(session),
scan_type: isHealthMode ? 'health_check' : 'identification',
demo_scans_used: guestScanCount,
demo_scans_remaining: demoScansRemaining,
});
const demoResult = await PlantRecognitionService.identify(imageUri, language, {
idempotencyKey: createIdempotencyKey('demo-scan-plant'),
});
setAnalysisProgress(100);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
await new Promise(resolve => setTimeout(resolve, 350));
incrementGuestScanCount();
setAnalysisResult(demoResult);
posthog.capture('demo_scan_completed', {
if (isDemoMode) {
posthog.capture('demo_scan_started', {
authenticated: Boolean(session),
scan_type: isHealthMode ? 'health_check' : 'identification',
demo_scans_used: guestScanCount,
demo_scans_remaining: demoScansRemaining,
});
const demoResult = await PlantRecognitionService.identify(imageUri, language, {
idempotencyKey: createIdempotencyKey('demo-scan-plant'),
});
setAnalysisProgress(100);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
await new Promise(resolve => setTimeout(resolve, 350));
incrementGuestScanCount();
setAnalysisResult(demoResult);
posthog.capture('demo_scan_completed', {
authenticated: Boolean(session),
latency_ms: Date.now() - startTime,
demo_scans_used_after: guestScanCount + 1,
@@ -600,7 +601,7 @@ export default function ScannerScreen() {
router.back();
return;
}
router.replace('/onboarding');
router.replace(hasCompletedOnboarding ? '/(tabs)' : '/onboarding');
};
const controlsPaddingBottom = Math.max(20, insets.bottom + 10);