diff --git a/apps/rebreak-native/app/debug.tsx b/apps/rebreak-native/app/debug.tsx index 1c6b68e..05f8b42 100644 --- a/apps/rebreak-native/app/debug.tsx +++ b/apps/rebreak-native/app/debug.tsx @@ -137,23 +137,13 @@ function PlanOverrideToggle({ if (plan === currentPlan) return; setLoading(true); try { - // PATCH /api/admin/users/:id requires admin privileges. - // If the dev-user is not admin, this returns 403 — see alert below. - await apiFetch(`/api/admin/users/${userId}`, { - method: 'PATCH', - body: JSON.stringify({ plan }), + await apiFetch('/api/dev/set-plan', { + method: 'POST', + body: { plan }, }); invalidateMe(); } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes('403')) { - Alert.alert( - 'Kein Admin-Zugriff', - 'PATCH /api/admin/users/:id setzt Admin-Rechte voraus. Plan manuell im Admin-Panel flippen.', - ); - } else { - Alert.alert('Fehler', msg); - } + Alert.alert('Fehler', e instanceof Error ? e.message : String(e)); } finally { setLoading(false); } @@ -196,7 +186,7 @@ function PlanOverrideToggle({ lineHeight: 17, }} > - PATCH /api/admin/users/:id — braucht Admin-Rechte + POST /api/dev/set-plan — nur staging diff --git a/apps/rebreak-native/app/settings.tsx b/apps/rebreak-native/app/settings.tsx index 7dd2bf7..3e08d42 100644 --- a/apps/rebreak-native/app/settings.tsx +++ b/apps/rebreak-native/app/settings.tsx @@ -1,5 +1,6 @@ import { Alert, + ActivityIndicator, Linking, Platform, ScrollView, @@ -20,6 +21,8 @@ import { useAuthStore } from '../stores/auth'; import { useThemeStore, type ThemeMode } from '../stores/theme'; import { useLanguageStore, type AppLanguage } from '../stores/language'; import { useUserPlan } from '../hooks/useUserPlan'; +import { useMe, invalidateMe, type Plan } from '../hooks/useMe'; +import { apiFetch } from '../lib/api'; import { AppHeader } from '../components/AppHeader'; // ─── Subscription Sheet ──────────────────────────────────────────────────── @@ -173,9 +176,12 @@ export default function SettingsScreen() { // For now: picker is wired to local state only, changes are NOT persisted. const [selectedVoice, setSelectedVoice] = useState('EXAVITQu4vr4xnSDxMaL'); + const { me } = useMe(); + // TrueSheet ref for Lyra-Voice picker (UISheetPresentationController bottom-sheet) const voiceSheetRef = useRef(null); const subscriptionSheetRef = useRef(null); + const planSheetRef = useRef(null); async function handleSignOut() { Alert.alert(t('auth.signOut'), '', [ @@ -360,6 +366,13 @@ export default function SettingsScreen() { sublabel: t('settings.debug_tts_desc'), soon: true, }, + { + icon: 'star-outline', + label: t('settings.debug_plan'), + sublabel: t('settings.debug_plan_desc'), + value: me?.plan ?? '…', + onPress: () => planSheetRef.current?.present(), + }, ], }); } @@ -596,6 +609,23 @@ export default function SettingsScreen() { + {__DEV__ && ( + + planSheetRef.current?.dismiss()} + /> + + )} + ); } + +// ─── Plan-Override Sheet (DEV only) ─────────────────────────────────────── + +const DEV_PLANS: Plan[] = ['free', 'pro', 'legend']; + +const DEV_PLAN_ACCENT: Record = { + free: '#737373', + pro: '#007AFF', + legend: '#f59e0b', +}; + +function PlanPickerSheetContent({ + currentPlan, + colors, + t, + onDone, +}: { + currentPlan: Plan; + colors: import('../lib/theme').ColorScheme; + t: (key: string) => string; + onDone: () => void; +}) { + const [loading, setLoading] = useState(false); + + async function pick(plan: Plan) { + if (plan === currentPlan || loading) return; + setLoading(true); + try { + await apiFetch('/api/dev/set-plan', { method: 'POST', body: { plan } }); + invalidateMe(); + onDone(); + } catch (e: unknown) { + Alert.alert(t('common.error'), e instanceof Error ? e.message : String(e)); + } finally { + setLoading(false); + } + } + + return ( + + + {t('settings.debug_plan')} + + + {t('settings.debug_plan_desc')} + + {DEV_PLANS.map((plan, idx) => { + const isActive = plan === currentPlan; + const accent = DEV_PLAN_ACCENT[plan]; + return ( + pick(plan)} + disabled={loading || isActive} + activeOpacity={0.6} + > + + + + + {plan} + + + {isActive ? ( + loading ? ( + + ) : ( + + ) + ) : null} + + + ); + })} + + ); +}