Add chat-first onboarding and intelligent context-based paywall
Replaces the hardcoded message limit with a model-driven paywall and removes registration before the first question: - Onboarding: the app opens directly into the chat. Dynamic conversation starters are served by GET /suggestions (services/api/suggestions.json), updatable with a deploy — no app release needed. Guests chat via POST /guest/chat, identified by an app-generated device id; sign-in moves to the paywall, where a purchase must attach to an account. - Free experience: the model runs in discovery mode — follow-up questions, pattern identification, visible understanding — building an analysis without delivering the full solution. - Intelligent paywall: the model returns structured output (reply + analysis_ready). Only when the problem is described, the information is sufficient and an action plan is ready does it write a calm transition and pause the conversation. Manufactured urgency, emotional pressure, fake readiness and mid-answer stops are explicitly forbidden. - Premium: on unlock the app resends the transcript and the backend immediately delivers the full analysis, strategies and exercises, then the dialogue continues without restriction. - The old FREE_MESSAGE_LIMIT becomes MESSAGE_CAP (default 200/30 days), kept purely as an abuse backstop — it is not the paywall. - WelcomeScreen removed; the app is now two views (Chat, Paywall). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118DaxZR36RpnY524vRqx3z
This commit is contained in:
+31
-15
@@ -1,29 +1,45 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { getAccessToken } from './src/auth/session';
|
||||
import type { ChatMessage } from './src/api/client';
|
||||
import { ChatScreen } from './src/screens/ChatScreen';
|
||||
import { PaywallScreen } from './src/screens/PaywallScreen';
|
||||
import { WelcomeScreen } from './src/screens/WelcomeScreen';
|
||||
|
||||
type View = 'loading' | 'welcome' | 'chat' | 'paywall';
|
||||
|
||||
/**
|
||||
* Three views, no navigation library. The backend decides when the paywall
|
||||
* appears (HTTP 402 on /chat).
|
||||
* Two views, no navigation library. The user lands directly in the chat —
|
||||
* no registration before the first question. The backend decides the paywall
|
||||
* moment (the analysis is ready); after unlock the conversation continues
|
||||
* with the full analysis delivered immediately.
|
||||
*/
|
||||
export default function App() {
|
||||
const [view, setView] = useState<View>('loading');
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => setView(token ? 'chat' : 'welcome'));
|
||||
}, []);
|
||||
const [view, setView] = useState<'chat' | 'paywall'>('chat');
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [paywalled, setPaywalled] = useState(false);
|
||||
const [deliverAnalysis, setDeliverAnalysis] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar style="dark" />
|
||||
{view === 'welcome' && <WelcomeScreen onSignedIn={() => setView('chat')} />}
|
||||
{view === 'chat' && <ChatScreen onLimitReached={() => setView('paywall')} />}
|
||||
{view === 'paywall' && <PaywallScreen onSubscribed={() => setView('chat')} />}
|
||||
{view === 'chat' && (
|
||||
<ChatScreen
|
||||
messages={messages}
|
||||
setMessages={setMessages}
|
||||
paywalled={paywalled}
|
||||
onPaywallTriggered={() => setPaywalled(true)}
|
||||
onContinueWithPremium={() => setView('paywall')}
|
||||
deliverAnalysis={deliverAnalysis}
|
||||
onAnalysisDelivered={() => setDeliverAnalysis(false)}
|
||||
/>
|
||||
)}
|
||||
{view === 'paywall' && (
|
||||
<PaywallScreen
|
||||
onSubscribed={() => {
|
||||
setPaywalled(false);
|
||||
setDeliverAnalysis(true);
|
||||
setView('chat');
|
||||
}}
|
||||
onDismiss={() => setView('chat')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user