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:
Millian Lamiaux
2026-04-17 18:56:24 +02:00
parent e0e02c4550
commit 791f432334
176 changed files with 16508 additions and 2305 deletions

View File

@@ -1,6 +1,7 @@
/**
* TabataFit Workout Complete Screen
* Celebration with real data from activity store
* Dark Medical design system — navy, green, no glass
*/
import { useRef, useEffect, useMemo, useState } from 'react'
@@ -15,7 +16,6 @@ import {
} from 'react-native'
import { useRouter, useLocalSearchParams } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { BlurView } from 'expo-blur'
import { Icon, type IconName } from '@/src/shared/components/Icon'
import * as Sharing from 'expo-sharing'
@@ -26,15 +26,17 @@ import { useActivityStore, useUserStore } from '@/src/shared/stores'
import { getWorkoutById, getPopularWorkouts, getTrainerById, getWorkoutAccentColor } from '@/src/shared/data'
import { useTranslatedWorkout, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
import { SyncConsentModal } from '@/src/shared/components/SyncConsentModal'
import { NativeButton } from '@/src/shared/components/native'
import { enableSync } from '@/src/shared/services/sync'
import type { WorkoutSessionData } from '@/src/shared/types'
import { useThemeColors, BRAND } from '@/src/shared/theme'
import { useThemeColors } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
import { TYPOGRAPHY } from '@/src/shared/constants/typography'
import { TYPOGRAPHY, FONT_FAMILY } from '@/src/shared/constants/typography'
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
import { RADIUS } from '@/src/shared/constants/borderRadius'
import { SPRING, EASE } from '@/src/shared/constants/animations'
import { GREEN, NAVY, TEXT, BORDER_COLORS } from '@/src/shared/constants/colors'
const { width: SCREEN_WIDTH } = Dimensions.get('window')
@@ -79,7 +81,7 @@ function SecondaryButton({
style={{ width: '100%' }}
>
<Animated.View style={[styles.secondaryButton, { transform: [{ scale: scaleAnim }] }]}>
{icon && <Icon name={icon} size={18} tintColor={colors.text.primary} style={styles.buttonIcon} />}
{icon && <Icon name={icon} size={18} tintColor={TEXT.PRIMARY} style={styles.buttonIcon} />}
<RNText style={styles.secondaryButtonText}>{children}</RNText>
</Animated.View>
</Pressable>
@@ -94,7 +96,6 @@ function PrimaryButton({
children: React.ReactNode
}) {
const colors = useThemeColors()
const isDark = colors.colorScheme === 'dark'
const styles = useMemo(() => createStyles(colors), [colors])
const scaleAnim = useRef(new Animated.Value(1)).current
@@ -124,10 +125,10 @@ function PrimaryButton({
<Animated.View
style={[
styles.primaryButton,
{ backgroundColor: isDark ? '#FFFFFF' : '#000000', transform: [{ scale: scaleAnim }] },
{ backgroundColor: GREEN['500'], transform: [{ scale: scaleAnim }] },
]}
>
<RNText style={[styles.primaryButtonText, { color: isDark ? '#000000' : '#FFFFFF' }]}>
<RNText style={[styles.primaryButtonText, { color: NAVY['900'] }]}>
{children}
</RNText>
</Animated.View>
@@ -217,8 +218,7 @@ function StatCard({
return (
<Animated.View style={[styles.statCard, { transform: [{ scale: scaleAnim }] }]}>
<BlurView intensity={colors.glass.blurMedium} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
<Icon name={icon} size={24} tintColor={accentColor} />
<Icon name={icon} size={24} tintColor={GREEN['500']} />
<RNText style={styles.statValue}>{value}</RNText>
<RNText style={styles.statLabel}>{label}</RNText>
</Animated.View>
@@ -248,9 +248,9 @@ function BurnBarResult({ percentile, accentColor }: { percentile: number; accent
return (
<View style={styles.burnBarContainer}>
<RNText style={styles.burnBarTitle}>{t('screens:complete.burnBar')}</RNText>
<RNText style={[styles.burnBarResult, { color: accentColor }]}>{t('screens:complete.burnBarResult', { percentile })}</RNText>
<RNText style={[styles.burnBarResult, { color: GREEN['500'] }]}>{t('screens:complete.burnBarResult', { percentile })}</RNText>
<View style={styles.burnBarTrack}>
<Animated.View style={[styles.burnBarFill, { width: barWidth, backgroundColor: accentColor }]} />
<Animated.View style={[styles.burnBarFill, { width: barWidth, backgroundColor: GREEN['500'] }]} />
</View>
</View>
)
@@ -383,20 +383,20 @@ export default function WorkoutCompleteScreen() {
{/* Stats Grid */}
<View style={styles.statsGrid}>
<StatCard value={resultCalories} label={t('screens:complete.caloriesLabel')} icon="flame.fill" accentColor={trainerColor} delay={100} />
<StatCard value={resultMinutes} label={t('screens:complete.minutesLabel')} icon="clock.fill" accentColor={trainerColor} delay={200} />
<StatCard value="100%" label={t('screens:complete.completeLabel')} icon="checkmark.circle.fill" accentColor={trainerColor} delay={300} />
<StatCard value={resultCalories} label={t('screens:complete.caloriesLabel')} icon="flame.fill" accentColor={GREEN['500']} delay={100} />
<StatCard value={resultMinutes} label={t('screens:complete.minutesLabel')} icon="clock.fill" accentColor={GREEN['500']} delay={200} />
<StatCard value="100%" label={t('screens:complete.completeLabel')} icon="checkmark.circle.fill" accentColor={GREEN['500']} delay={300} />
</View>
{/* Burn Bar */}
<BurnBarResult percentile={burnBarPercentile} accentColor={trainerColor} />
<BurnBarResult percentile={burnBarPercentile} accentColor={GREEN['500']} />
<View style={styles.divider} />
{/* Streak */}
<View style={styles.streakSection}>
<View style={[styles.streakBadge, { backgroundColor: trainerColor + '26' }]}>
<Icon name="flame.fill" size={32} tintColor={trainerColor} />
<View style={[styles.streakBadge, { backgroundColor: GREEN.DIM }]}>
<Icon name="flame.fill" size={32} tintColor={GREEN['500']} />
</View>
<View style={styles.streakInfo}>
<RNText style={styles.streakTitle}>{t('screens:complete.streakTitle', { count: streak.current })}</RNText>
@@ -408,9 +408,13 @@ export default function WorkoutCompleteScreen() {
{/* Share Button */}
<View style={styles.shareSection}>
<SecondaryButton onPress={handleShare} icon="square.and.arrow.up">
{t('screens:complete.shareWorkout')}
</SecondaryButton>
<NativeButton
variant="ghost"
title={t('screens:complete.shareWorkout')}
systemImage="square.and.arrow.up"
onPress={handleShare}
fullWidth
/>
</View>
<View style={styles.divider} />
@@ -425,9 +429,8 @@ export default function WorkoutCompleteScreen() {
onPress={() => handleWorkoutPress(w.id)}
style={styles.recommendedCard}
>
<BlurView intensity={colors.glass.blurLight} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
<View style={[styles.recommendedThumb, { backgroundColor: trainerColor + '20' }]}>
<Icon name="flame.fill" size={24} tintColor={trainerColor} />
<View style={[styles.recommendedThumb, { backgroundColor: GREEN.DIM }]}>
<Icon name="flame.fill" size={24} tintColor={GREEN['500']} />
</View>
<RNText style={styles.recommendedTitleText} numberOfLines={1}>{w.title}</RNText>
<RNText style={styles.recommendedDurationText}>{t('units.minUnit', { count: w.duration })}</RNText>
@@ -439,11 +442,14 @@ export default function WorkoutCompleteScreen() {
{/* Fixed Bottom Button */}
<View style={[styles.bottomBar, { paddingBottom: insets.bottom + SPACING[4] }]}>
<BlurView intensity={colors.glass.blurHeavy} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
<View style={styles.homeButtonContainer}>
<PrimaryButton onPress={handleGoHome}>
{t('screens:complete.backToHome')}
</PrimaryButton>
<NativeButton
variant="primary"
title={t('screens:complete.backToHome')}
onPress={handleGoHome}
fullWidth
controlSize="large"
/>
</View>
</View>
@@ -481,27 +487,27 @@ function createStyles(colors: ThemeColors) {
justifyContent: 'center',
paddingVertical: SPACING[3],
paddingHorizontal: SPACING[4],
borderRadius: RADIUS.LG,
borderRadius: RADIUS.MD,
borderWidth: 1,
borderColor: colors.border.glassStrong,
borderColor: BORDER_COLORS.DIM,
backgroundColor: 'transparent',
},
secondaryButtonText: {
...TYPOGRAPHY.BODY,
color: colors.text.primary,
fontWeight: '600',
color: TEXT.PRIMARY,
fontFamily: FONT_FAMILY.SANS_SEMIBOLD,
},
primaryButton: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: SPACING[4],
paddingHorizontal: SPACING[6],
borderRadius: RADIUS.LG,
borderRadius: RADIUS.MD,
overflow: 'hidden',
},
primaryButtonText: {
...TYPOGRAPHY.HEADLINE,
fontWeight: '700',
fontFamily: FONT_FAMILY.SANS_BOLD,
},
buttonIcon: {
marginRight: SPACING[2],
@@ -518,7 +524,7 @@ function createStyles(colors: ThemeColors) {
},
celebrationTitle: {
...TYPOGRAPHY.TITLE_1,
color: colors.text.primary,
color: TEXT.PRIMARY,
letterSpacing: 2,
},
ringsContainer: {
@@ -530,23 +536,21 @@ function createStyles(colors: ThemeColors) {
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: colors.border.glass,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 2,
borderColor: colors.border.glassStrong,
},
ring1: {
borderColor: BRAND.PRIMARY,
backgroundColor: 'rgba(255, 107, 53, 0.15)',
borderColor: GREEN['500'],
backgroundColor: GREEN.DIM,
},
ring2: {
borderColor: '#30D158',
backgroundColor: 'rgba(48, 209, 88, 0.15)',
borderColor: GREEN['500'],
backgroundColor: GREEN.DIM,
},
ring3: {
borderColor: '#5AC8FA',
backgroundColor: 'rgba(90, 200, 250, 0.15)',
borderColor: GREEN['500'],
backgroundColor: GREEN.DIM,
},
ringEmoji: {
fontSize: 28,
@@ -562,19 +566,20 @@ function createStyles(colors: ThemeColors) {
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[4]) / 3,
padding: SPACING[3],
borderRadius: RADIUS.LG,
backgroundColor: colors.surface.default.backgroundColor,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.border.glass,
borderColor: colors.surface.default.borderColor,
overflow: 'hidden',
},
statValue: {
...TYPOGRAPHY.TITLE_1,
color: colors.text.primary,
color: TEXT.PRIMARY,
marginTop: SPACING[2],
},
statLabel: {
...TYPOGRAPHY.CAPTION_2,
color: colors.text.tertiary,
color: TEXT.TERTIARY,
marginTop: SPACING[1],
},
@@ -584,7 +589,7 @@ function createStyles(colors: ThemeColors) {
},
burnBarTitle: {
...TYPOGRAPHY.HEADLINE,
color: colors.text.tertiary,
color: TEXT.TERTIARY,
},
burnBarResult: {
...TYPOGRAPHY.BODY,
@@ -594,18 +599,18 @@ function createStyles(colors: ThemeColors) {
burnBarTrack: {
height: 8,
backgroundColor: colors.bg.surface,
borderRadius: 4,
borderRadius: RADIUS.SM,
overflow: 'hidden',
},
burnBarFill: {
height: '100%',
borderRadius: 4,
borderRadius: RADIUS.SM,
},
// Divider
divider: {
height: 1,
backgroundColor: colors.border.glass,
backgroundColor: BORDER_COLORS.DIM,
marginVertical: SPACING[2],
},
@@ -619,7 +624,7 @@ function createStyles(colors: ThemeColors) {
streakBadge: {
width: 64,
height: 64,
borderRadius: 32,
borderRadius: RADIUS.FULL,
alignItems: 'center',
justifyContent: 'center',
},
@@ -628,11 +633,11 @@ function createStyles(colors: ThemeColors) {
},
streakTitle: {
...TYPOGRAPHY.TITLE_2,
color: colors.text.primary,
color: TEXT.PRIMARY,
},
streakSubtitle: {
...TYPOGRAPHY.BODY,
color: colors.text.tertiary,
color: TEXT.TERTIARY,
marginTop: SPACING[1],
},
@@ -648,7 +653,7 @@ function createStyles(colors: ThemeColors) {
},
recommendedTitle: {
...TYPOGRAPHY.HEADLINE,
color: colors.text.primary,
color: TEXT.PRIMARY,
marginBottom: SPACING[4],
},
recommendedGrid: {
@@ -660,7 +665,8 @@ function createStyles(colors: ThemeColors) {
padding: SPACING[3],
borderRadius: RADIUS.LG,
borderWidth: 1,
borderColor: colors.border.glass,
borderColor: colors.surface.default.borderColor,
backgroundColor: colors.surface.default.backgroundColor,
overflow: 'hidden',
},
recommendedThumb: {
@@ -674,15 +680,15 @@ function createStyles(colors: ThemeColors) {
},
recommendedInitial: {
...TYPOGRAPHY.TITLE_1,
color: colors.text.primary,
color: TEXT.PRIMARY,
},
recommendedTitleText: {
...TYPOGRAPHY.CARD_TITLE,
color: colors.text.primary,
color: TEXT.PRIMARY,
},
recommendedDurationText: {
...TYPOGRAPHY.CARD_METADATA,
color: colors.text.tertiary,
color: TEXT.TERTIARY,
},
// Bottom Bar
@@ -693,8 +699,9 @@ function createStyles(colors: ThemeColors) {
right: 0,
paddingHorizontal: LAYOUT.SCREEN_PADDING,
paddingTop: SPACING[4],
backgroundColor: colors.bg.base,
borderTopWidth: 1,
borderTopColor: colors.border.glass,
borderTopColor: BORDER_COLORS.DIM,
},
homeButtonContainer: {
height: 56,