UI readability pass and Bazaar in-app purchase bridge
- muted text #9e9ec0 -> #727299 (contrast 2.6 -> 4.5 on white); tab labels 11px - map: chapter titles wrap to two lines instead of being cut off - reduced-motion respected (MotionConfig + CSS), focus-visible ring, no pull-to-refresh, short vibration on correct/wrong answers - src/lib/bazaar.ts: bridge to the Android WebView wrapper; checkout uses Bazaar IAP only inside that app, Zarinpal everywhere else (unchanged) - android/: WebView wrapper with Poolakey; written but never built Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { AnimatePresence, MotionConfig, motion } from 'motion/react';
|
||||
import { api, ApiError, http, type Grade, type UserProfile } from '@/api';
|
||||
import { AppShell } from '@/components/AppShell';
|
||||
import { ToastProvider } from '@/components/Toast';
|
||||
@@ -83,52 +83,55 @@ export function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell>
|
||||
<ToastProvider>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={flow.kind}
|
||||
className="flex h-full flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{flow.kind === 'boot' && <SplashScreen />}
|
||||
{flow.kind === 'bootError' && (
|
||||
<div className="flex h-full flex-col justify-center">
|
||||
<ErrorState error={flow.error} onRetry={() => void boot()} />
|
||||
</div>
|
||||
)}
|
||||
{flow.kind === 'onboarding' && (
|
||||
<OnboardingScreen
|
||||
onDone={() => {
|
||||
store.set(KEYS.onboarded, '1');
|
||||
setFlow({ kind: 'login' });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{flow.kind === 'login' && <LoginScreen onDone={() => void afterLogin()} />}
|
||||
{flow.kind === 'setup' && (
|
||||
<SetupScreen
|
||||
profile={flow.profile}
|
||||
grades={flow.grades}
|
||||
onDone={(profile) => setFlow({ kind: 'main', profile, grades: flow.grades })}
|
||||
/>
|
||||
)}
|
||||
{flow.kind === 'main' && (
|
||||
<MainApp
|
||||
profile={flow.profile}
|
||||
grades={flow.grades}
|
||||
payment={launchPayment}
|
||||
onSignedOut={() => setFlow({ kind: 'login' })}
|
||||
onRegistered={trackReferral}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</ToastProvider>
|
||||
</AppShell>
|
||||
// reducedMotion="user": اگر «کاهشِ حرکت» در گوشی روشن باشد، motion جابهجاییها را حذف میکند.
|
||||
<MotionConfig reducedMotion="user">
|
||||
<AppShell>
|
||||
<ToastProvider>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={flow.kind}
|
||||
className="flex h-full flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{flow.kind === 'boot' && <SplashScreen />}
|
||||
{flow.kind === 'bootError' && (
|
||||
<div className="flex h-full flex-col justify-center">
|
||||
<ErrorState error={flow.error} onRetry={() => void boot()} />
|
||||
</div>
|
||||
)}
|
||||
{flow.kind === 'onboarding' && (
|
||||
<OnboardingScreen
|
||||
onDone={() => {
|
||||
store.set(KEYS.onboarded, '1');
|
||||
setFlow({ kind: 'login' });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{flow.kind === 'login' && <LoginScreen onDone={() => void afterLogin()} />}
|
||||
{flow.kind === 'setup' && (
|
||||
<SetupScreen
|
||||
profile={flow.profile}
|
||||
grades={flow.grades}
|
||||
onDone={(profile) => setFlow({ kind: 'main', profile, grades: flow.grades })}
|
||||
/>
|
||||
)}
|
||||
{flow.kind === 'main' && (
|
||||
<MainApp
|
||||
profile={flow.profile}
|
||||
grades={flow.grades}
|
||||
payment={launchPayment}
|
||||
onSignedOut={() => setFlow({ kind: 'login' })}
|
||||
onRegistered={trackReferral}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</ToastProvider>
|
||||
</AppShell>
|
||||
</MotionConfig>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user