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