diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 9e123ac..23230a7 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -20,10 +20,13 @@ "@ts-rest/core": "^3.51.0", "better-auth": "^1.2.7", "expo": "~54.0.10", + "expo-linear-gradient": "~15.0.7", "expo-status-bar": "~3.0.8", + "lucide-react-native": "^0.544.0", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.4", + "react-native-svg": "^15.13.0", "react-native-web": "~0.21.1", "zod": "^3.24.1" }, diff --git a/apps/mobile/src/components/AccountScreen.tsx b/apps/mobile/src/components/AccountScreen.tsx index d6eb09f..2417c81 100644 --- a/apps/mobile/src/components/AccountScreen.tsx +++ b/apps/mobile/src/components/AccountScreen.tsx @@ -202,7 +202,7 @@ export default function AccountScreen({ onTabPress }: AccountScreenProps) { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, header: { alignItems: 'center', @@ -233,8 +233,10 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', - backgroundColor: 'rgba(255, 255, 255, 0.08)', - borderRadius: 16, + backgroundColor: '#282c38', + borderRadius: 20, + borderWidth: 1, + borderColor: '#414551', paddingHorizontal: 20, paddingVertical: 18, marginBottom: 12, @@ -283,10 +285,10 @@ const styles = StyleSheet.create({ marginTop: 16, }, logoutItem: { - backgroundColor: 'rgba(231, 76, 60, 0.15)', + backgroundColor: '#282c38', }, logoutText: { - color: '#E74C3C', + color: '#FFFFFF', fontWeight: '600', }, bottomSpacing: { diff --git a/apps/mobile/src/components/BottomNavigation.tsx b/apps/mobile/src/components/BottomNavigation.tsx index 8fafe64..a7b6707 100644 --- a/apps/mobile/src/components/BottomNavigation.tsx +++ b/apps/mobile/src/components/BottomNavigation.tsx @@ -4,35 +4,13 @@ import { TouchableOpacity, StyleSheet, } from 'react-native'; +import { Notebook, User, Dumbbell } from 'lucide-react-native'; interface BottomNavigationProps { activeTab: 'pr' | 'dashboard' | 'account'; onTabPress: (tab: 'pr' | 'dashboard' | 'account') => void; } -// PR Icon Component (Trophy/Medal icon) -const PRIcon = ({ active }: { active: boolean }) => ( - - {/* Trophy base */} - - {/* Trophy cup */} - - {/* Trophy handles */} - - - -); - -// Account Icon Component (User profile icon) -const AccountIcon = ({ active }: { active: boolean }) => ( - - {/* Head */} - - {/* Body */} - - -); - export default function BottomNavigation({ activeTab, onTabPress }: BottomNavigationProps) { return ( @@ -43,7 +21,10 @@ export default function BottomNavigation({ activeTab, onTabPress }: BottomNaviga onPress={() => onTabPress('pr')} activeOpacity={0.7} > - + {/* Dashboard Button (Center/Main) */} @@ -52,12 +33,10 @@ export default function BottomNavigation({ activeTab, onTabPress }: BottomNaviga onPress={() => onTabPress('dashboard')} activeOpacity={0.7} > - - - + {/* Account Button */} @@ -66,7 +45,10 @@ export default function BottomNavigation({ activeTab, onTabPress }: BottomNaviga onPress={() => onTabPress('account')} activeOpacity={0.7} > - + @@ -77,13 +59,13 @@ const styles = StyleSheet.create({ container: { paddingBottom: 40, // Safe area for iPhone paddingTop: 20, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, navigationBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', paddingTop: 0, paddingHorizontal: 20, marginHorizontal: 0, @@ -99,100 +81,4 @@ const styles = StyleSheet.create({ centerButton: { marginHorizontal: 20, }, - centerIcon: { - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: '#FFFFFF', - alignItems: 'center', - justifyContent: 'center', - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 4, - }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 8, - }, - activeCenterIcon: { - backgroundColor: '#F0F0F0', - }, - playIcon: { - width: 0, - height: 0, - backgroundColor: 'transparent', - borderStyle: 'solid', - borderTopWidth: 12, - borderRightWidth: 0, - borderBottomWidth: 12, - borderLeftWidth: 20, - borderTopColor: 'transparent', - borderRightColor: 'transparent', - borderBottomColor: 'transparent', - borderLeftColor: '#000000', - marginLeft: 4, - }, - // Icon wrapper - iconWrapper: { - width: 28, - height: 28, - alignItems: 'center', - justifyContent: 'center', - }, - - // Trophy Icon Styles - trophyBase: { - width: 12, - height: 4, - position: 'absolute', - bottom: 0, - }, - trophyCup: { - width: 12, - height: 14, - borderRadius: 6, - position: 'absolute', - top: 0, - }, - trophyHandleLeft: { - position: 'absolute', - left: -2, - top: 4, - width: 6, - height: 8, - borderWidth: 2, - borderRightWidth: 0, - borderTopLeftRadius: 4, - borderBottomLeftRadius: 4, - backgroundColor: 'transparent', - }, - trophyHandleRight: { - position: 'absolute', - right: -2, - top: 4, - width: 6, - height: 8, - borderWidth: 2, - borderLeftWidth: 0, - borderTopRightRadius: 4, - borderBottomRightRadius: 4, - backgroundColor: 'transparent', - }, - - // User Icon Styles - userHead: { - width: 10, - height: 10, - borderRadius: 5, - position: 'absolute', - top: 0, - }, - userBody: { - width: 16, - height: 10, - borderRadius: 8, - position: 'absolute', - bottom: 0, - }, }); \ No newline at end of file diff --git a/apps/mobile/src/components/DashboardCarousel.tsx b/apps/mobile/src/components/DashboardCarousel.tsx index 5cecf25..0e9783a 100644 --- a/apps/mobile/src/components/DashboardCarousel.tsx +++ b/apps/mobile/src/components/DashboardCarousel.tsx @@ -222,7 +222,7 @@ const styles = StyleSheet.create({ height: height * 0.6, // 60% of screen height for card content borderRadius: 20, borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.2)', + borderColor: '#414551', shadowColor: '#000', shadowOffset: { width: 0, diff --git a/apps/mobile/src/components/DashboardScreen.tsx b/apps/mobile/src/components/DashboardScreen.tsx index fdc80cb..af00f19 100644 --- a/apps/mobile/src/components/DashboardScreen.tsx +++ b/apps/mobile/src/components/DashboardScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, @@ -10,10 +10,53 @@ import BottomNavigation from './BottomNavigation'; import AccountScreen from './AccountScreen'; import PRScreen from './PRScreen'; import TrainingScreen from './TrainingScreen'; +import { authClient } from '../lib/auth-client'; +import { api } from '../lib/api'; +import type { AthleteDetailsDto } from '@dropit/schemas'; export default function DashboardScreen() { const [activeTab, setActiveTab] = useState<'pr' | 'dashboard' | 'account'>('dashboard'); const [showTraining, setShowTraining] = useState(false); + const [athleteData, setAthleteData] = useState(null); + const [athleteId, setAthleteId] = useState(null); + + // Fetch athleteId from session on mount + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = await authClient.getSession(); + if (sessionData.data?.session?.athleteId) { + setAthleteId(sessionData.data.session.athleteId); + } + } catch (error) { + console.error('Error fetching session:', error); + } + }; + fetchSession(); + }, []); + + // Fetch athlete data when athleteId is available + useEffect(() => { + const fetchAthleteData = async () => { + if (!athleteId) return; + + try { + const response = await api.athlete.getAthlete({ + params: { id: athleteId }, + }); + + const data = typeof response.body === 'string' ? JSON.parse(response.body) : response.body; + + if (response.status === 200) { + setAthleteData(data); + } + } catch (error) { + console.error('Error fetching athlete data:', error); + } + }; + + fetchAthleteData(); + }, [athleteId]); const handleTabPress = (tab: 'pr' | 'dashboard' | 'account') => { @@ -55,7 +98,7 @@ export default function DashboardScreen() { {/* Greeting */} - Bonjour, Clovis + Bonjour, {athleteData?.firstName || 'Athlète'} {/* Carousel Content */} @@ -75,7 +118,7 @@ export default function DashboardScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, header: { paddingHorizontal: 24, diff --git a/apps/mobile/src/components/LoginScreen.tsx b/apps/mobile/src/components/LoginScreen.tsx index 1cc3fba..5452122 100644 --- a/apps/mobile/src/components/LoginScreen.tsx +++ b/apps/mobile/src/components/LoginScreen.tsx @@ -147,7 +147,7 @@ export default function LoginScreen({ onLoginSuccess }: LoginScreenProps) { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, scrollContainer: { flexGrow: 1, diff --git a/apps/mobile/src/components/PRScreen.tsx b/apps/mobile/src/components/PRScreen.tsx index 530b9b6..1017b8e 100644 --- a/apps/mobile/src/components/PRScreen.tsx +++ b/apps/mobile/src/components/PRScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, @@ -9,40 +9,75 @@ import { Alert, } from 'react-native'; import { StatusBar } from 'expo-status-bar'; +import { Search } from 'lucide-react-native'; import BottomNavigation from './BottomNavigation'; +import { LinearGradient } from 'expo-linear-gradient'; +import { authClient } from '../lib/auth-client'; +import { api } from '../lib/api'; +import type { PersonalRecordDto } from '@dropit/schemas'; interface PRScreenProps { onTabPress: (tab: 'pr' | 'dashboard' | 'account') => void; } -interface PRRecord { - id: string; - exerciseName: string; - value: number; - unit: string; -} - -const mockPRRecords: PRRecord[] = [ - { id: '1', exerciseName: 'Arraché', value: 120, unit: 'kg' }, - { id: '2', exerciseName: 'Epaulé-Jetté', value: 162, unit: 'kg' }, - { id: '3', exerciseName: 'Squat Nuque', value: 200, unit: 'kg' }, - { id: '4', exerciseName: 'Squat clav', value: 180, unit: 'kg' }, - { id: '5', exerciseName: 'Tirage arraché', value: 140, unit: 'kg' }, - { id: '6', exerciseName: 'Tirage épaulé', value: 170, unit: 'kg' }, - { id: '7', exerciseName: 'Jetté', value: 167, unit: 'kg' }, -]; - export default function PRScreen({ onTabPress }: PRScreenProps) { const [searchText, setSearchText] = useState(''); - const [filteredRecords, setFilteredRecords] = useState(mockPRRecords); + const [allRecords, setAllRecords] = useState([]); + const [filteredRecords, setFilteredRecords] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [athleteId, setAthleteId] = useState(null); + + // Fetch athleteId from session on mount + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = await authClient.getSession(); + if (sessionData.data?.session?.athleteId) { + setAthleteId(sessionData.data.session.athleteId); + } + } catch (error) { + console.error('Error fetching session:', error); + } + }; + fetchSession(); + }, []); + + // Fetch personal records when athleteId is available + useEffect(() => { + const fetchPersonalRecords = async () => { + if (!athleteId) return; + + setIsLoading(true); + try { + const response = await api.personalRecord.getAthletePersonalRecords({ + params: { id: athleteId }, + }); + + const data = typeof response.body === 'string' ? JSON.parse(response.body) : response.body; + + if (response.status === 200) { + setAllRecords(data); + setFilteredRecords(data); + } + } catch (error) { + console.error('Error fetching personal records:', error); + setAllRecords([]); + setFilteredRecords([]); + } finally { + setIsLoading(false); + } + }; + + fetchPersonalRecords(); + }, [athleteId]); const handleSearch = (text: string) => { setSearchText(text); if (text === '') { - setFilteredRecords(mockPRRecords); + setFilteredRecords(allRecords); } else { - const filtered = mockPRRecords.filter(record => - record.exerciseName.toLowerCase().includes(text.toLowerCase()) + const filtered = allRecords.filter(record => + record.exerciseName?.toLowerCase().includes(text.toLowerCase()) ); setFilteredRecords(filtered); } @@ -52,14 +87,14 @@ export default function PRScreen({ onTabPress }: PRScreenProps) { Alert.alert('Ajouter un record', 'Fonctionnalité à implémenter'); }; - const handleRecordPress = (record: PRRecord) => { + const handleRecordPress = (record: PersonalRecordDto) => { Alert.alert( - record.exerciseName, - `Record actuel: ${record.value}${record.unit}\n\nFonctionnalité de modification à implémenter` + record.exerciseName || 'Exercice', + `Record actuel: ${record.weight}kg\n\nFonctionnalité de modification à implémenter` ); }; - const renderPRCard = (record: PRRecord, index: number) => ( + const renderPRCard = (record: PersonalRecordDto, index: number) => ( handleRecordPress(record)} activeOpacity={0.8} > - {record.exerciseName} + {record.exerciseName || 'Exercice'} - {record.value} - {record.unit} + {record.weight} + kg ); @@ -92,7 +127,7 @@ export default function PRScreen({ onTabPress }: PRScreenProps) { {/* Search Bar */} - + {/* PR Records Grid */} - - {filteredRecords.map((record, index) => renderPRCard(record, index))} - + {isLoading ? ( + + Chargement... + + ) : filteredRecords.length > 0 ? ( + + {filteredRecords.map((record, index) => renderPRCard(record, index))} + + ) : ( + + Aucun record + + {searchText + ? 'Aucun record trouvé pour cette recherche' + : 'Vous n\'avez pas encore de records personnels'} + + + )} {/* Add New Record Button */} - - - - - - Ajoutez un nouveau record + + + Ajoutez un nouveau record + + {/* Bottom spacing */} @@ -137,7 +187,7 @@ export default function PRScreen({ onTabPress }: PRScreenProps) { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, header: { alignItems: 'center', @@ -148,7 +198,7 @@ const styles = StyleSheet.create({ title: { fontSize: 24, fontWeight: 'bold', - color: '#FFFFFF', + color: '#e9edf5', }, content: { flex: 1, @@ -162,22 +212,16 @@ const styles = StyleSheet.create({ searchBar: { flexDirection: 'row', alignItems: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.1)', - borderRadius: 25, + backgroundColor: '#282c38', + borderRadius: 20, paddingHorizontal: 20, paddingVertical: 14, - }, - searchIcon: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: 'rgba(255, 255, 255, 0.5)', - marginRight: 12, + gap: 12, }, searchInput: { flex: 1, fontSize: 16, - color: '#FFFFFF', + color: '#e9edf5', fontWeight: '400', }, @@ -188,9 +232,31 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', marginBottom: 32, }, + + // Empty State + emptyState: { + paddingVertical: 60, + paddingHorizontal: 32, + alignItems: 'center', + }, + emptyStateTitle: { + fontSize: 20, + fontWeight: 'bold', + color: '#f2f6f6', + marginBottom: 12, + textAlign: 'center', + }, + emptyStateText: { + fontSize: 16, + color: '#a9acae', + textAlign: 'center', + lineHeight: 24, + }, prCard: { - backgroundColor: 'rgba(255, 255, 255, 0.08)', - borderRadius: 16, + backgroundColor: '#282c38', + borderWidth: 1, + borderColor: '#414551', + borderRadius: 20, padding: 20, marginBottom: 16, alignItems: 'center', @@ -206,15 +272,15 @@ const styles = StyleSheet.create({ exerciseName: { fontSize: 16, fontWeight: '500', - color: '#FFFFFF', + color: '#e9edf5', textAlign: 'center', marginBottom: 12, lineHeight: 22, }, recordValue: { - fontSize: 32, + fontSize: 28, fontWeight: 'bold', - color: '#FFFFFF', + color: '#e9edf5', }, unit: { fontSize: 18, @@ -224,40 +290,17 @@ const styles = StyleSheet.create({ // Add Record Button addRecordButton: { - flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.12)', - borderRadius: 16, + borderRadius: 20, paddingVertical: 20, paddingHorizontal: 24, marginBottom: 24, }, - addIcon: { - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - marginRight: 12, - }, - addIconHorizontal: { - position: 'absolute', - width: 16, - height: 2, - backgroundColor: '#FFFFFF', - borderRadius: 1, - }, - addIconVertical: { - position: 'absolute', - width: 2, - height: 16, - backgroundColor: '#FFFFFF', - borderRadius: 1, - }, addRecordText: { fontSize: 16, fontWeight: '500', - color: '#FFFFFF', + color: '#e9edf5', }, bottomSpacing: { diff --git a/apps/mobile/src/components/TrainingDetailScreen.tsx b/apps/mobile/src/components/TrainingDetailScreen.tsx index dfd7212..cae6dbd 100644 --- a/apps/mobile/src/components/TrainingDetailScreen.tsx +++ b/apps/mobile/src/components/TrainingDetailScreen.tsx @@ -8,6 +8,8 @@ import { Alert, } from 'react-native'; import { StatusBar } from 'expo-status-bar'; +import { ChevronLeft, Play, RotateCcw } from 'lucide-react-native'; +import Svg, { Circle } from 'react-native-svg'; import type { WorkoutDto } from '@dropit/schemas'; interface TrainingDetailScreenProps { @@ -21,26 +23,21 @@ export default function TrainingDetailScreen({ }: TrainingDetailScreenProps) { const [isTimerActive, setIsTimerActive] = useState(false); const [timeLeft, setTimeLeft] = useState(0); + const [showTimer, setShowTimer] = useState(false); // Extract display info from element const name = element.type === 'exercise' ? element.exercise.name : element.complex.exercises.map((e: { name: string }) => e.name).join(', '); - const sets = `${element.sets} x ${element.reps}`; + const sets = `${element.sets} sets`; + const reps = `${element.reps} reps` const weight = element.startWeight_percent ? `${element.startWeight_percent}%` : '-'; - const recovery = element.rest ? `${element.rest}sec` : '2min'; + const recovery = element.rest ? `${element.rest}sec` : '90sec'; const instructions = element.description || `Instructions pour ${name}.`; const videoUrl = element.type === 'exercise' ? element.exercise.video : undefined; - // Parse recovery time to seconds - const parseRecoveryTime = (recovery: string) => { - if (recovery.includes('min')) { - const minutes = parseInt(recovery); - const seconds = recovery.includes('sec') ? parseInt(recovery.split('min')[1]) || 0 : 0; - return minutes * 60 + seconds; - } - return parseInt(recovery) || 120; // default 2 minutes - }; + // Get default rest time in seconds (from element.rest or 90s default) + const defaultRestTime = element.rest || 90; useEffect(() => { let interval: ReturnType; if (isTimerActive && timeLeft > 0) { @@ -49,33 +46,29 @@ export default function TrainingDetailScreen({ }, 1000); } else if (timeLeft === 0 && isTimerActive) { setIsTimerActive(false); + setShowTimer(false); Alert.alert('Temps écoulé !', 'Temps de repos terminé'); } return () => clearInterval(interval); }, [isTimerActive, timeLeft]); - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = seconds % 60; - return `${mins}:${secs.toString().padStart(2, '0')}`; - }; - const handlePlayTimer = () => { - if (!isTimerActive) { - const recoverySeconds = parseRecoveryTime(recovery); - setTimeLeft(recoverySeconds); - setIsTimerActive(true); - } else { - setIsTimerActive(false); + if (timeLeft === 0) { + setTimeLeft(defaultRestTime); } + setIsTimerActive(true); + setShowTimer(true); }; - const handleTimerSelect = (minutes: number) => { - const seconds = minutes * 60; - setTimeLeft(seconds); - if (isTimerActive) { - setIsTimerActive(false); - } + const handleTimerPress = () => { + setIsTimerActive(false); + setShowTimer(false); + }; + + const handleResetTimer = () => { + setTimeLeft(0); + setIsTimerActive(false); + setShowTimer(false); }; return ( @@ -85,92 +78,122 @@ export default function TrainingDetailScreen({ {/* Header */} - + DROPIT - - - + - - {/* Video Section */} - - - - + + + {/* Video Section */} + + + + + - - - {/* Exercise Title */} - - {name} - - {/* Timer Display */} - - - {isTimerActive || timeLeft > 0 ? formatTime(timeLeft) : '0 To 90'} - - + {/* Exercise Title */} + + {name} + - {/* Time Selection Buttons */} - - handleTimerSelect(1)} - > - - 1 min - - - handleTimerSelect(2)} - > - - 2 min - - - handleTimerSelect(3)} - > - - 3 min - - - + {/* Info Display */} + + + + {sets} + + + + + {reps} + + + + + {weight} + + + - {/* Exercise Details */} - - Stimulus - - {sets} répétitions - {weight} - {recovery} de repos - + {/* Exercise Details */} + + + Instructions + + {instructions} + - Instructions - - {instructions} - + Montée en charge + + + + 2 reps à vide + + + + 2 reps à 20% + + + + 2 reps à 40% + + + + 2 reps à 60% + + + + 2 reps à 80% + + + + {/* Bottom Controls */} - Easy{'\n'}Mode + Chrono{'\n'}Repos - - - + {showTimer ? ( + + + + + + {timeLeft} + + + ) : ( + + + + )} - - + + @@ -184,62 +207,45 @@ export default function TrainingDetailScreen({ const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, - paddingTop: 60, - paddingBottom: 16, + paddingTop: 40, + paddingBottom: 8, }, backButton: { width: 40, - height: 40, alignItems: 'center', justifyContent: 'center', }, - backIcon: { - width: 0, - height: 0, - borderTopWidth: 8, - borderBottomWidth: 8, - borderRightWidth: 12, - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderRightColor: '#FFFFFF', - }, appTitle: { - fontSize: 20, + fontSize: 18, fontWeight: 'bold', color: '#FFFFFF', letterSpacing: 1, }, - settingsButton: { + placeholder: { width: 40, - height: 40, - alignItems: 'center', - justifyContent: 'center', - }, - settingsIcon: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: 'rgba(255, 255, 255, 0.6)', }, content: { flex: 1, }, + scrollContent: { + flexGrow: 1, + justifyContent: 'space-between', + }, + topContent: { + flexShrink: 1, + }, // Video Section videoContainer: { - height: 300, + height: 250, backgroundColor: '#2A2A2A', - marginHorizontal: 24, - borderRadius: 12, - marginBottom: 32, - overflow: 'hidden', }, videoPlaceholder: { flex: 1, @@ -255,96 +261,95 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - playIcon: { - width: 0, - height: 0, - borderTopWidth: 12, - borderBottomWidth: 12, - borderLeftWidth: 20, - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderLeftColor: '#1A1A1A', - marginLeft: 4, - }, // Exercise Header exerciseHeader: { - paddingHorizontal: 24, - marginBottom: 24, + paddingVertical: 16, }, exerciseTitle: { - fontSize: 32, + fontSize: 16, fontWeight: 'bold', - color: '#FFFFFF', + color: '#e9edf5', textAlign: 'center', }, - // Timer Display + // Timer Display (center button) timerDisplay: { + width: 65, + height: 65, alignItems: 'center', - marginBottom: 24, + justifyContent: 'center', + position: 'relative', + }, + progressCircle: { + position: 'absolute', }, timerText: { - fontSize: 48, + fontSize: 20, fontWeight: 'bold', - color: '#FFFFFF', + color: '#e9edf5', }, // Time Buttons - timeButtonsContainer: { + numbersContainer: { flexDirection: 'row', justifyContent: 'center', paddingHorizontal: 24, - marginBottom: 32, + marginBottom: 20, gap: 16, }, - timeButton: { + numbersElement: { paddingHorizontal: 24, paddingVertical: 12, - borderRadius: 25, - backgroundColor: 'rgba(255, 255, 255, 0.2)', - borderWidth: 2, - borderColor: 'transparent', - }, - activeTimeButton: { - borderColor: '#4A9EFF', - backgroundColor: 'rgba(74, 158, 255, 0.2)', + borderWidth:1, + borderRadius: 20, + borderColor: '#6387d9' }, - timeButtonText: { - fontSize: 16, + numbersText: { + fontSize: 12, fontWeight: '500', - color: 'rgba(255, 255, 255, 0.7)', - }, - activeTimeButtonText: { - color: '#4A9EFF', + color: '#e9edf5', }, // Details Section detailsContainer: { - paddingHorizontal: 24, + flex: 1, + paddingHorizontal: 12, + paddingVertical: 12, marginBottom: 32, + marginHorizontal: 12, + backgroundColor: '#282c38', + borderRadius: 20, }, sectionTitle: { - fontSize: 20, + fontSize: 12, fontWeight: '600', - color: '#FFFFFF', + color: '#a2a6b2', marginBottom: 8, + marginTop: 4, }, - exerciseSubtitle: { + sectionText: { fontSize: 16, - color: 'rgba(255, 255, 255, 0.8)', - marginBottom: 24, + color: '#e9edf5', + marginBottom: 4, }, - instructionsTitle: { - fontSize: 18, - fontWeight: '500', - color: 'rgba(255, 255, 255, 0.9)', - marginBottom: 12, + listContainer: { + gap: 4, + }, + listItem: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + dot: { + width: 6, + height: 6, + borderRadius: 3, + backgroundColor: '#5d88ee', }, - instructionsText: { + listText: { fontSize: 16, - color: 'rgba(255, 255, 255, 0.8)', - lineHeight: 24, + color: '#e9edf5', }, // Bottom Controls @@ -359,15 +364,15 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingVertical: 8, }, - easyModeText: { - fontSize: 14, - color: 'rgba(255, 255, 255, 0.7)', + chronoRestText: { + fontSize: 12, + color: '#e9edf5', textAlign: 'center', lineHeight: 18, }, playButton: { - width: 80, - height: 80, + width: 65, + height: 65, borderRadius: 40, backgroundColor: 'transparent', borderWidth: 3, @@ -375,35 +380,10 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - activePlayButton: { - backgroundColor: 'rgba(74, 158, 255, 0.2)', - }, - playIconLarge: { - width: 0, - height: 0, - borderTopWidth: 16, - borderBottomWidth: 16, - borderLeftWidth: 24, - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderLeftColor: '#4A9EFF', - marginLeft: 4, - }, - pauseIcon: { - width: 24, - height: 24, - backgroundColor: '#4A9EFF', - }, fullscreenButton: { alignItems: 'center', paddingVertical: 8, }, - fullscreenIcon: { - width: 24, - height: 24, - backgroundColor: 'rgba(255, 255, 255, 0.6)', - borderRadius: 4, - }, bottomSpacing: { height: 40, diff --git a/apps/mobile/src/components/TrainingScreen.tsx b/apps/mobile/src/components/TrainingScreen.tsx index ceee167..65ace1f 100644 --- a/apps/mobile/src/components/TrainingScreen.tsx +++ b/apps/mobile/src/components/TrainingScreen.tsx @@ -7,6 +7,7 @@ import { ScrollView, } from 'react-native'; import { StatusBar } from 'expo-status-bar'; +import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import TrainingDetailScreen from './TrainingDetailScreen'; import { authClient } from '../lib/auth-client'; import { api } from '../lib/api'; @@ -150,7 +151,7 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { ); } - const renderExerciseBlock = (element: WorkoutDto['elements'][number], displayInfo: { id: string; name: string; sets: string; weight: string; recovery: string }) => ( + const renderExerciseBlock = (element: WorkoutDto['elements'][number], displayInfo: { id: string; name: string; sets: string; weight: string; rest: string }) => ( {displayInfo.name} - {displayInfo.sets} • {displayInfo.weight} • {displayInfo.recovery} + {displayInfo.sets} • {displayInfo.weight} • {displayInfo.rest} {/* Arrow Icon */} - + ); @@ -189,10 +190,10 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { {/* Header */} - + - Entraînement + Entraînements @@ -237,11 +238,6 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { - {/* Full Date Title */} - - {formatDateFull(selectedDate)} - - {isLoading ? ( @@ -251,7 +247,6 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { <> {/* Training Info */} - {trainingData.workout?.title || 'Entraînement'} {trainingData.workout?.description && ( {trainingData.workout.description} )} @@ -270,7 +265,7 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { name, sets: `${element.sets} x ${element.reps}`, weight: element.startWeight_percent ? `${element.startWeight_percent}%` : '-', - recovery: element.rest ? `${element.rest}sec` : '-', + rest: element.rest ? `${element.rest}sec` : '-', }); })} @@ -294,7 +289,7 @@ export default function TrainingScreen({ onBack }: TrainingScreenProps) { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1A1A1A', + backgroundColor: '#191d26', }, header: { flexDirection: 'row', @@ -310,16 +305,6 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - backIcon: { - width: 0, - height: 0, - borderTopWidth: 8, - borderBottomWidth: 8, - borderRightWidth: 12, - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderRightColor: '#FFFFFF', - }, headerContent: { flex: 1, alignItems: 'center', @@ -327,7 +312,7 @@ const styles = StyleSheet.create({ title: { fontSize: 20, fontWeight: 'bold', - color: '#FFFFFF', + color: '#f2f6f6', textAlign: 'center', }, placeholder: { @@ -336,7 +321,7 @@ const styles = StyleSheet.create({ // Date Carousel dateCarouselContainer: { - paddingVertical: 16, + paddingVertical: 8, backgroundColor: '#1A1A1A', }, dateCarousel: { @@ -347,28 +332,30 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingVertical: 12, paddingHorizontal: 16, - borderRadius: 12, + borderRadius: 20, minWidth: 70, }, dateItemSelected: { - backgroundColor: 'rgba(255, 255, 255, 0.15)', + backgroundColor: '#282c38', + borderWidth: 1, + borderColor: '#6387d9' }, dateNumber: { - fontSize: 24, + fontSize: 16, fontWeight: 'bold', - color: 'rgba(255, 255, 255, 0.6)', + color: '#91989a', marginBottom: 4, }, dateNumberSelected: { - color: '#FFFFFF', + color: '#f2f6f6', }, dateMonth: { - fontSize: 12, - color: 'rgba(255, 255, 255, 0.6)', + fontSize: 8, + color: '#91989a', fontWeight: '600', }, dateMonthSelected: { - color: '#FFFFFF', + color: '#f2f6f6', }, trainingIndicator: { position: 'absolute', @@ -379,18 +366,6 @@ const styles = StyleSheet.create({ backgroundColor: '#3B82F6', }, - // Full Date Title - fullDateContainer: { - paddingHorizontal: 24, - paddingVertical: 16, - }, - fullDateText: { - fontSize: 18, - fontWeight: 'bold', - color: '#FFFFFF', - textAlign: 'center', - }, - content: { flex: 1, paddingHorizontal: 24, @@ -398,24 +373,24 @@ const styles = StyleSheet.create({ // Training Info trainingInfo: { - marginBottom: 24, + paddingVertical: 24, }, trainingTitle: { fontSize: 20, fontWeight: 'bold', - color: '#FFFFFF', + color: '#eff4f8', textAlign: 'center', marginBottom: 8, }, trainingDescription: { fontSize: 14, - color: 'rgba(255, 255, 255, 0.7)', + color: '#a7acae', textAlign: 'center', marginBottom: 12, }, trainingSubtitle: { fontSize: 16, - color: 'rgba(255, 255, 255, 0.7)', + color: '#a7acae', textAlign: 'center', }, @@ -430,13 +405,13 @@ const styles = StyleSheet.create({ emptyStateTitle: { fontSize: 20, fontWeight: 'bold', - color: '#FFFFFF', + color: '#f2f6f6', marginBottom: 12, textAlign: 'center', }, emptyStateText: { fontSize: 16, - color: 'rgba(255, 255, 255, 0.7)', + color: '#a9acae', textAlign: 'center', lineHeight: 24, }, @@ -448,10 +423,12 @@ const styles = StyleSheet.create({ exerciseBlock: { flexDirection: 'row', alignItems: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.08)', + backgroundColor: '#282c38', borderRadius: 16, - padding: 16, + padding: 4, marginBottom: 12, + borderWidth: 1, + borderColor: '#414551' }, // Exercise Image @@ -461,7 +438,7 @@ const styles = StyleSheet.create({ imagePlaceholder: { width: 60, height: 60, - borderRadius: 12, + borderRadius: 14, }, exerciseImagePlaceholder: { backgroundColor: '#3498DB', @@ -476,9 +453,9 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, exerciseName: { - fontSize: 18, + fontSize: 12, fontWeight: '600', - color: '#FFFFFF', + color: '#eff4f8', marginBottom: 6, }, exerciseDetails: { @@ -486,8 +463,8 @@ const styles = StyleSheet.create({ alignItems: 'center', }, detailText: { - fontSize: 14, - color: 'rgba(255, 255, 255, 0.7)', + fontSize: 12, + color: '#a7acae', fontWeight: '400', }, @@ -497,16 +474,6 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - arrowIcon: { - width: 0, - height: 0, - borderTopWidth: 6, - borderBottomWidth: 6, - borderLeftWidth: 10, - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderLeftColor: 'rgba(255, 255, 255, 0.5)', - }, bottomSpacing: { height: 40, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a38f561..88feb9c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,9 +183,15 @@ importers: expo: specifier: ~54.0.10 version: 54.0.12(@babel/core@7.28.4)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-linear-gradient: + specifier: ~15.0.7 + version: 15.0.7(expo@54.0.12(@babel/core@7.28.4)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-status-bar: specifier: ~3.0.8 version: 3.0.8(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + lucide-react-native: + specifier: ^0.544.0 + version: 0.544.0(react-native-svg@15.13.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: specifier: 19.1.0 version: 19.1.0 @@ -195,6 +201,9 @@ importers: react-native: specifier: 0.81.4 version: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0) + react-native-svg: + specifier: ^15.13.0 + version: 15.13.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: specifier: ~0.21.1 version: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -3434,6 +3443,9 @@ packages: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bplist-creator@0.1.0: resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} @@ -3793,6 +3805,17 @@ packages: css-in-js-utils@3.1.0: resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -3926,6 +3949,19 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv-cli@10.0.0: resolution: {integrity: sha512-lnOnttzfrzkRx2echxJHQRB6vOAMSCzzZg79IxpC00tU42wZPuZkQxNNrrwVAxaQZIIh001l4PxVlCrBxngBzA==} hasBin: true @@ -3992,6 +4028,10 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-editor@0.4.2: resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} engines: {node: '>=8'} @@ -4160,6 +4200,13 @@ packages: expo: '*' react: '*' + expo-linear-gradient@15.0.7: + resolution: {integrity: sha512-yF+y+9Shpr/OQFfy/wglB/0bykFMbwHBTuMRa5Of/r2P1wbkcacx8rg0JsUWkXH/rn2i2iWdubyqlxSJa3ggZA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + expo-modules-autolinking@3.0.14: resolution: {integrity: sha512-/qh1ru2kGPOycGvE9dXEKJZbPmYA5U5UcAlWWFbcq9+VhhWdZWZ0zs7V2JCdl+OvpBDo1y9WbqPP5VHQSYqT+Q==} hasBin: true @@ -5145,6 +5192,13 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lucide-react-native@0.544.0: + resolution: {integrity: sha512-ylN6TfmVmZVAd82CmTWyKnYv8e5WQLvaaabOHIB5z3OMA2Vg/jtMW5ORDoR2J7Hr8LMSys1v2b2Fr1GVxr3XQA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-native: '*' + react-native-svg: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + lucide-react@0.539.0: resolution: {integrity: sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==} peerDependencies: @@ -5173,6 +5227,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -5500,6 +5557,9 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -5978,6 +6038,12 @@ packages: react: '*' react-native: '*' + react-native-svg@15.13.0: + resolution: {integrity: sha512-/YPK+PAAXg4T0x2d2vYPvqqAhOYid2bRKxUVT7STIyd1p2JxWmsGQkfZxXCkEFN7TwLfIyVlT5RimT91Pj/qXw==} + peerDependencies: + react: '*' + react-native: '*' + react-native-web@0.21.1: resolution: {integrity: sha512-BeNsgwwe4AXUFPAoFU+DKjJ+CVQa3h54zYX77p7GVZrXiiNo3vl03WYDYVEy5R2J2HOPInXtQZB5gmj3vuzrKg==} peerDependencies: @@ -6989,6 +7055,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + warn-once@0.1.1: + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} + watchpack@2.4.4: resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} @@ -10825,6 +10894,8 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@1.0.0: {} + bplist-creator@0.1.0: dependencies: stream-buffers: 2.2.0 @@ -11195,6 +11266,21 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -11274,6 +11360,24 @@ snapshots: dependencies: esutils: 2.0.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv-cli@10.0.0: dependencies: cross-spawn: 7.0.6 @@ -11329,6 +11433,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@4.5.0: {} + env-editor@0.4.2: {} error-ex@1.3.4: @@ -11566,6 +11672,12 @@ snapshots: expo: 54.0.12(@babel/core@7.28.4)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 + expo-linear-gradient@15.0.7(expo@54.0.12(@babel/core@7.28.4)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + expo: 54.0.12(@babel/core@7.28.4)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0) + expo-modules-autolinking@3.0.14: dependencies: '@expo/spawn-async': 1.7.2 @@ -12801,6 +12913,12 @@ snapshots: dependencies: yallist: 4.0.0 + lucide-react-native@0.544.0(react-native-svg@15.13.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0) + react-native-svg: 15.13.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + lucide-react@0.539.0(react@19.0.0): dependencies: react: 19.0.0 @@ -12827,6 +12945,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.0.14: {} + media-typer@0.3.0: {} media-typer@1.1.0: {} @@ -13336,6 +13456,10 @@ snapshots: dependencies: path-key: 3.1.1 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + nullthrows@1.1.1: {} oauth-sign@0.9.0: {} @@ -13774,6 +13898,14 @@ snapshots: react: 19.1.0 react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0) + react-native-svg@15.13.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + css-select: 5.2.2 + css-tree: 1.1.3 + react: 19.1.0 + react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0) + warn-once: 0.1.1 + react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.28.4 @@ -14846,6 +14978,8 @@ snapshots: dependencies: makeerror: 1.0.12 + warn-once@0.1.1: {} + watchpack@2.4.4: dependencies: glob-to-regexp: 0.4.1