Store readiness: in-app account deletion, privacy policy, terms, listing copy
Closes the remaining store-compliance gaps before App Store / Play submission: - Account deletion (App Store guideline 5.1.1): DELETE /me removes the user row (usage cascades). The app exposes it through one quiet 'Account' caption link under the chat, visible only when signed in, driving two native dialogs (Sign out / Delete account with a destructive confirm) — no new views, no menus, minimalism intact. Deletion signs out and resets the app; copy notes that store subscriptions are cancelled in App Store / Play settings. - docs/store/privacy-policy.md: the complete data inventory (matching the actual schema), transient OpenAI processing with no training, no profiling or ads, GDPR legal bases and rights, in-app erasure. - docs/store/terms-of-service.md: not-therapy positioning with crisis guidance, AI-generated-content caveat, 18+ eligibility, auto-renewal/cancellation terms, liability, Swedish governing law. - docs/store/listing.md: App Store and Play copy written to the honest-claims rule (subtitle 'Think Beyond Thought', keywords, descriptions), plus App Privacy and Data safety questionnaire mappings. - LAUNCH.md updated: host the policy/terms, set store URLs, use the prepared listing copy. - Tests: 30 passing (adds DELETE /me coverage). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118DaxZR36RpnY524vRqx3z
This commit is contained in:
@@ -89,6 +89,11 @@ export async function fetchMe(): Promise<Me> {
|
||||
return request<Me>('/me', {}, await authHeader());
|
||||
}
|
||||
|
||||
/** Permanently deletes the account and its usage data (App Store 5.1.1). */
|
||||
export async function deleteAccount(): Promise<void> {
|
||||
await request<{ deleted: boolean }>('/me', { method: 'DELETE' }, await authHeader());
|
||||
}
|
||||
|
||||
export async function verifyPurchase(body: {
|
||||
platform: 'ios' | 'android';
|
||||
productId: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
@@ -9,7 +10,8 @@ import {
|
||||
TextInput,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { fetchSuggestions, sendChat, type ChatMessage } from '../api/client';
|
||||
import { deleteAccount, fetchSuggestions, sendChat, type ChatMessage } from '../api/client';
|
||||
import { signOut } from '../auth/session';
|
||||
import { colors, spacing, type } from '../theme';
|
||||
|
||||
interface Props {
|
||||
@@ -22,6 +24,9 @@ interface Props {
|
||||
/** Set right after unlock: fetch the promised analysis automatically. */
|
||||
deliverAnalysis: boolean;
|
||||
onAnalysisDelivered: () => void;
|
||||
/** Shows the quiet Account affordance (sign out / delete account). */
|
||||
signedIn: boolean;
|
||||
onSignedOut: () => void;
|
||||
}
|
||||
|
||||
const SUGGESTIONS_SHOWN = 4;
|
||||
@@ -39,6 +44,8 @@ export function ChatScreen({
|
||||
onContinueWithPremium,
|
||||
deliverAnalysis,
|
||||
onAnalysisDelivered,
|
||||
signedIn,
|
||||
onSignedOut,
|
||||
}: Props) {
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [draft, setDraft] = useState('');
|
||||
@@ -92,6 +99,42 @@ export function ChatScreen({
|
||||
|
||||
const showSuggestions = messages.length === 0 && suggestions.length > 0;
|
||||
|
||||
// No menus, no settings view: account management lives in two native
|
||||
// dialogs behind one quiet link (in-app deletion per App Store 5.1.1).
|
||||
function openAccount() {
|
||||
Alert.alert('Account', undefined, [
|
||||
{
|
||||
text: 'Sign out',
|
||||
onPress: () => {
|
||||
signOut().then(onSignedOut);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Delete account',
|
||||
style: 'destructive',
|
||||
onPress: () =>
|
||||
Alert.alert(
|
||||
'Delete account?',
|
||||
'This permanently deletes your account and usage data. Conversations are never stored. An active subscription is cancelled in your App Store or Google Play settings.',
|
||||
[
|
||||
{
|
||||
text: 'Delete',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
deleteAccount()
|
||||
.then(() => signOut())
|
||||
.then(onSignedOut)
|
||||
.catch(() => setError('Something went wrong. Please try again.'));
|
||||
},
|
||||
},
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
],
|
||||
),
|
||||
},
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
]);
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
@@ -150,6 +193,11 @@ export function ChatScreen({
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
{signedIn ? (
|
||||
<Pressable style={styles.accountLink} onPress={openAccount}>
|
||||
<Text style={styles.accountText}>Account</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
@@ -191,8 +239,10 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'flex-end',
|
||||
gap: spacing.s,
|
||||
paddingHorizontal: spacing.l,
|
||||
paddingBottom: spacing.xl,
|
||||
paddingBottom: spacing.m,
|
||||
},
|
||||
accountLink: { alignItems: 'center', paddingBottom: spacing.l },
|
||||
accountText: { ...type.caption },
|
||||
input: {
|
||||
flex: 1,
|
||||
...type.body,
|
||||
|
||||
Reference in New Issue
Block a user