refactor: code quality cleanup — remove any types, add logger, rename Kine to Tabata
- Phase 0: Rename all Kine references to Tabata (types, files, imports, i18n, analytics events) - Phase 1: Add test coverage for tabataProgramStore, workoutProgramStore, and color utils (47 tests) - Phase 2: Remove all `any` types from production code with proper typed replacements - Phase 3: Replace ~60 raw console.* calls with __DEV__-gated logger utility - Phase 4: Verify .DS_Store housekeeping (already clean) 0 TypeScript errors, 583/583 tests passing.
This commit is contained in:
@@ -24,12 +24,16 @@ import { useUserStore } from '@/src/shared/stores'
|
||||
import { OnboardingStep } from '@/src/shared/components/OnboardingStep'
|
||||
import { StyledText } from '@/src/shared/components/StyledText'
|
||||
|
||||
import { useThemeColors, BRAND, PHASE } from '@/src/shared/theme'
|
||||
import { useThemeColors } from '@/src/shared/theme'
|
||||
import type { ThemeColors } from '@/src/shared/theme/types'
|
||||
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
|
||||
import { RADIUS } from '@/src/shared/constants/borderRadius'
|
||||
import { DURATION, EASE, SPRING } from '@/src/shared/constants/animations'
|
||||
import { track, identifyUser, setUserProperties, trackScreen } from '@/src/shared/services/analytics'
|
||||
import { GREEN, NAVY, BORDER_COLORS } from '@/src/shared/constants/colors'
|
||||
import { withOpacity } from '@/src/shared/utils/color'
|
||||
import { PHASE } from '@/src/shared/constants/colors'
|
||||
import { NativeButton } from '@/src/shared/components/native'
|
||||
|
||||
import type { FitnessLevel, FitnessGoal, WeeklyFrequency } from '@/src/shared/types'
|
||||
|
||||
@@ -85,7 +89,7 @@ function ProblemScreen({ onNext }: { onNext: () => void }) {
|
||||
marginBottom: SPACING[8],
|
||||
}}
|
||||
>
|
||||
<Icon name="clock.fill" size={80} color={BRAND.PRIMARY} />
|
||||
<Icon name="clock.fill" size={80} color={GREEN[500]} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View style={{ opacity: textOpacity, alignItems: 'center' }}>
|
||||
@@ -122,7 +126,7 @@ function ProblemScreen({ onNext }: { onNext: () => void }) {
|
||||
onNext()
|
||||
}}
|
||||
>
|
||||
<StyledText size={17} weight="semibold" color="#FFFFFF">
|
||||
<StyledText size={17} weight="semibold" color={NAVY[900]}>
|
||||
{t('onboarding.problem.cta')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
@@ -190,7 +194,7 @@ function EmpathyScreen({
|
||||
<Icon
|
||||
name={item.icon}
|
||||
size={28}
|
||||
color={selected ? BRAND.PRIMARY : colors.text.tertiary}
|
||||
color={selected ? GREEN[500] : colors.text.tertiary}
|
||||
/>
|
||||
<StyledText
|
||||
size={15}
|
||||
@@ -219,7 +223,7 @@ function EmpathyScreen({
|
||||
<StyledText
|
||||
size={17}
|
||||
weight="semibold"
|
||||
color={barriers.length > 0 ? '#FFFFFF' : colors.text.disabled}
|
||||
color={barriers.length > 0 ? NAVY[900] : colors.text.disabled}
|
||||
>
|
||||
{t('common:continue')}
|
||||
</StyledText>
|
||||
@@ -280,7 +284,7 @@ function SolutionScreen({ onNext }: { onNext: () => void }) {
|
||||
<View style={styles.comparisonContainer}>
|
||||
{/* Tabata bar */}
|
||||
<View style={styles.barColumn}>
|
||||
<StyledText size={22} weight="bold" color={BRAND.PRIMARY}>
|
||||
<StyledText size={22} weight="bold" color={GREEN[500]}>
|
||||
{t('onboarding.solution.tabataCalories')}
|
||||
</StyledText>
|
||||
<View style={styles.barTrack}>
|
||||
@@ -359,7 +363,7 @@ function SolutionScreen({ onNext }: { onNext: () => void }) {
|
||||
onNext()
|
||||
}}
|
||||
>
|
||||
<StyledText size={17} weight="semibold" color="#FFFFFF">
|
||||
<StyledText size={17} weight="semibold" color={NAVY[900]}>
|
||||
{t('onboarding.solution.cta')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
@@ -373,7 +377,7 @@ function SolutionScreen({ onNext }: { onNext: () => void }) {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const WOW_FEATURES = [
|
||||
{ icon: 'timer' as const, iconColor: BRAND.PRIMARY, titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
|
||||
{ icon: 'timer' as const, iconColor: GREEN[500], titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
|
||||
{ icon: 'dumbbell' as const, iconColor: PHASE.REST, titleKey: 'onboarding.wow.card2Title', subtitleKey: 'onboarding.wow.card2Subtitle' },
|
||||
{ icon: 'mic' as const, iconColor: PHASE.PREP, titleKey: 'onboarding.wow.card3Title', subtitleKey: 'onboarding.wow.card3Subtitle' },
|
||||
{ icon: 'arrow.up.right' as const, iconColor: PHASE.COMPLETE, titleKey: 'onboarding.wow.card4Title', subtitleKey: 'onboarding.wow.card4Subtitle' },
|
||||
@@ -452,7 +456,7 @@ function WowScreen({ onNext }: { onNext: () => void }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={[wowStyles.iconCircle, { backgroundColor: `${feature.iconColor}26` }]}>
|
||||
<View style={[wowStyles.iconCircle, { backgroundColor: withOpacity(feature.iconColor, 0.15) }]}>
|
||||
<Icon name={feature.icon} size={22} color={feature.iconColor} />
|
||||
</View>
|
||||
<View style={wowStyles.textCol}>
|
||||
@@ -479,7 +483,7 @@ function WowScreen({ onNext }: { onNext: () => void }) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledText size={17} weight="semibold" color="#FFFFFF">
|
||||
<StyledText size={17} weight="semibold" color={NAVY[900]}>
|
||||
{t('common:next')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
@@ -659,7 +663,7 @@ function PersonalizationScreen({
|
||||
</View>
|
||||
|
||||
{name.trim().length > 0 && (
|
||||
<StyledText size={15} color={BRAND.SUCCESS} style={styles.readyMessage}>
|
||||
<StyledText size={15} color={GREEN[500]} style={styles.readyMessage}>
|
||||
{t('onboarding.personalization.readyMessage')}
|
||||
</StyledText>
|
||||
)}
|
||||
@@ -678,7 +682,7 @@ function PersonalizationScreen({
|
||||
<StyledText
|
||||
size={17}
|
||||
weight="semibold"
|
||||
color={name.trim() ? '#FFFFFF' : colors.text.disabled}
|
||||
color={name.trim() ? NAVY[900] : colors.text.disabled}
|
||||
>
|
||||
{t('common:continue')}
|
||||
</StyledText>
|
||||
@@ -822,7 +826,7 @@ function PaywallScreen({
|
||||
key={featureKey}
|
||||
style={[styles.featureRow, { opacity: featureAnims[i] }]}
|
||||
>
|
||||
<Icon name="checkmark.circle.fill" size={22} color={BRAND.SUCCESS} />
|
||||
<Icon name="checkmark.circle.fill" size={22} color={GREEN[500]} />
|
||||
<StyledText
|
||||
size={16}
|
||||
color={colors.text.primary}
|
||||
@@ -846,7 +850,7 @@ function PaywallScreen({
|
||||
onPress={() => handlePlanSelect('premium-yearly')}
|
||||
>
|
||||
<View style={styles.bestValueBadge}>
|
||||
<StyledText size={11} weight="bold" color="#FFFFFF">
|
||||
<StyledText size={11} weight="bold" color={NAVY[900]}>
|
||||
{t('onboarding.paywall.bestValue')}
|
||||
</StyledText>
|
||||
</View>
|
||||
@@ -856,7 +860,7 @@ function PaywallScreen({
|
||||
<StyledText size={13} color={colors.text.secondary}>
|
||||
{t('common:units.perYear')}
|
||||
</StyledText>
|
||||
<StyledText size={12} weight="semibold" color={BRAND.PRIMARY} style={{ marginTop: SPACING[1] }}>
|
||||
<StyledText size={12} weight="semibold" color={GREEN[500]} style={{ marginTop: SPACING[1] }}>
|
||||
{t('onboarding.paywall.savePercent')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
@@ -886,7 +890,7 @@ function PaywallScreen({
|
||||
onPress={handlePurchase}
|
||||
disabled={isPurchasing}
|
||||
>
|
||||
<StyledText size={17} weight="bold" color="#FFFFFF">
|
||||
<StyledText size={17} weight="bold" color={NAVY[900]}>
|
||||
{isPurchasing ? '...' : t('onboarding.paywall.trialCta')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
@@ -906,8 +910,8 @@ function PaywallScreen({
|
||||
</Pressable>
|
||||
|
||||
{/* Skip */}
|
||||
<Pressable
|
||||
style={styles.skipButton}
|
||||
<Pressable
|
||||
style={styles.skipButton}
|
||||
testID="skip-paywall"
|
||||
onPress={() => {
|
||||
track('onboarding_paywall_skipped')
|
||||
@@ -1125,8 +1129,8 @@ function createStyles(colors: ThemeColors) {
|
||||
// CTA Button
|
||||
ctaButton: {
|
||||
height: LAYOUT.BUTTON_HEIGHT,
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
borderRadius: RADIUS.GLASS_BUTTON,
|
||||
backgroundColor: GREEN[500],
|
||||
borderRadius: RADIUS.MD,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -1146,12 +1150,14 @@ function createStyles(colors: ThemeColors) {
|
||||
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[3]) / 2,
|
||||
paddingVertical: SPACING[6],
|
||||
alignItems: 'center',
|
||||
borderRadius: RADIUS.GLASS_CARD,
|
||||
...colors.glass.base,
|
||||
borderRadius: RADIUS.LG,
|
||||
backgroundColor: NAVY[800],
|
||||
borderWidth: 1,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
},
|
||||
barrierCardSelected: {
|
||||
borderColor: BRAND.PRIMARY,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.1)',
|
||||
borderColor: GREEN.BORDER,
|
||||
backgroundColor: GREEN.DIM,
|
||||
},
|
||||
|
||||
// ── Screen 3: Comparison ──
|
||||
@@ -1181,7 +1187,7 @@ function createStyles(colors: ThemeColors) {
|
||||
borderRadius: RADIUS.SM,
|
||||
},
|
||||
barTabata: {
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
backgroundColor: GREEN[500],
|
||||
},
|
||||
barCardio: {
|
||||
backgroundColor: PHASE.REST,
|
||||
@@ -1222,7 +1228,7 @@ function createStyles(colors: ThemeColors) {
|
||||
color: colors.text.primary,
|
||||
fontSize: 17,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
},
|
||||
segmentRow: {
|
||||
flexDirection: 'row',
|
||||
@@ -1268,16 +1274,18 @@ function createStyles(colors: ThemeColors) {
|
||||
paddingVertical: SPACING[5],
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: RADIUS.GLASS_CARD,
|
||||
...colors.glass.base,
|
||||
borderRadius: RADIUS.LG,
|
||||
backgroundColor: NAVY[800],
|
||||
borderWidth: 1,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
},
|
||||
pricingCardSelected: {
|
||||
borderColor: BRAND.PRIMARY,
|
||||
borderColor: GREEN.BORDER,
|
||||
borderWidth: 2,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.08)',
|
||||
backgroundColor: GREEN.DIM,
|
||||
},
|
||||
bestValueBadge: {
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
backgroundColor: GREEN[500],
|
||||
paddingHorizontal: SPACING[3],
|
||||
paddingVertical: SPACING[1],
|
||||
borderRadius: RADIUS.SM,
|
||||
@@ -1285,8 +1293,8 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
trialButton: {
|
||||
height: LAYOUT.BUTTON_HEIGHT,
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
borderRadius: RADIUS.GLASS_BUTTON,
|
||||
backgroundColor: GREEN[500],
|
||||
borderRadius: RADIUS.MD,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: SPACING[6],
|
||||
@@ -1322,7 +1330,7 @@ function createWowStyles(colors: ThemeColors) {
|
||||
iconCircle: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
borderRadius: RADIUS.FULL,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user