first commit

This commit is contained in:
elnatansamuel25 2026-02-18 23:39:51 +03:00
parent 3058e27685
commit 0503230fd4
20 changed files with 16029 additions and 44 deletions

48
App.tsx
View File

@ -1,45 +1,13 @@
/** import React from 'react';
* Sample React Native App import { NavigationContainer } from '@react-navigation/native';
* https://github.com/facebook/react-native import BottomTabNavigator from './src/navigation/BottomTabNavigator';
*
* @format
*/
import { NewAppScreen } from '@react-native/new-app-screen';
import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
function App() {
const isDarkMode = useColorScheme() === 'dark';
const App: React.FC = () => {
return ( return (
<SafeAreaProvider> <NavigationContainer>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <BottomTabNavigator />
<AppContent /> </NavigationContainer>
</SafeAreaProvider>
); );
} };
function AppContent() {
const safeAreaInsets = useSafeAreaInsets();
return (
<View style={styles.container}>
<NewAppScreen
templateFileName="App.tsx"
safeAreaInsets={safeAreaInsets}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default App; export default App;

2297
ios/Podfile.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0C80B921A6F3F58F76C31292 /* libPods-fortune.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-fortune.a */; }; 0C80B921A6F3F58F76C31292 /* libPods-fortune.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-fortune.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
4F08206F5132F769BA13651A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -159,6 +160,7 @@
files = ( files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
4F08206F5132F769BA13651A /* PrivacyInfo.xcprivacy in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -370,6 +372,10 @@
); );
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"$(inherited)",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_CPLUSPLUSFLAGS = ( OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)", "$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG", "-DFOLLY_NO_CONFIG",
@ -377,8 +383,13 @@
"-DFOLLY_USE_LIBCPP=1", "-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRCT_REMOVE_LEGACY_ARCH=1",
); );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
USE_HERMES = true;
}; };
name = Debug; name = Debug;
}; };
@ -435,6 +446,10 @@
"\"$(inherited)\"", "\"$(inherited)\"",
); );
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = (
"$(inherited)",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_CPLUSPLUSFLAGS = ( OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)", "$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG", "-DFOLLY_NO_CONFIG",
@ -442,8 +457,12 @@
"-DFOLLY_USE_LIBCPP=1", "-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRCT_REMOVE_LEGACY_ARCH=1",
); );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
USE_HERMES = true;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:fortune.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -28,7 +28,6 @@
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<false/> <false/>
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
@ -36,6 +35,8 @@
</dict> </dict>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string></string> <string></string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>

12342
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,18 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@react-native/new-app-screen": "0.84.0",
"@react-navigation/bottom-tabs": "^7.14.0",
"@react-navigation/native": "^7.1.28",
"@react-navigation/native-stack": "^7.13.0",
"lucide-react-native": "^0.574.0",
"react": "19.2.3", "react": "19.2.3",
"react-native": "0.84.0", "react-native": "0.84.0",
"@react-native/new-app-screen": "0.84.0", "react-native-linear-gradient": "^2.8.3",
"react-native-safe-area-context": "^5.5.2" "react-native-safe-area-context": "^5.6.2",
"react-native-screens": "^4.23.0",
"react-native-svg": "^15.15.3",
"react-native-vector-icons": "^10.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",

View File

@ -0,0 +1,80 @@
import React from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { COLORS, SPACING, SIZES } from '../theme/theme';
import { CATEGORY_FILTERS } from '../constants/data';
const CategoryPills: React.FC = () => {
const [activeId, setActiveId] = React.useState('1');
return (
<View style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{CATEGORY_FILTERS.map(filter => {
const isActive = filter.id === activeId;
return (
<TouchableOpacity
key={filter.id}
style={[styles.pill, isActive && styles.pillActive]}
onPress={() => setActiveId(filter.id)}
activeOpacity={0.7}
>
<Text style={styles.pillIcon}>{filter.icon}</Text>
{filter.label ? (
<Text
style={[styles.pillLabel, isActive && styles.pillLabelActive]}
>
{filter.label}
</Text>
) : null}
</TouchableOpacity>
);
})}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingVertical: SPACING.xs,
},
scrollContent: {
paddingHorizontal: SPACING.md,
gap: SPACING.sm,
},
pill: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.sm,
borderRadius: SIZES.pillRadius,
backgroundColor: COLORS.surface,
gap: 6,
},
pillActive: {
backgroundColor: COLORS.surfaceLight,
},
pillIcon: {
fontSize: 16,
},
pillLabel: {
fontSize: 13,
fontWeight: '600',
color: COLORS.textSecondary,
},
pillLabelActive: {
color: COLORS.text,
},
});
export default CategoryPills;

View File

@ -0,0 +1,79 @@
import React from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import { COLORS, SPACING, SIZES } from '../theme/theme';
import { LEAGUES, League } from '../constants/data';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const CARD_WIDTH = (SCREEN_WIDTH - SPACING.md * 2 - SPACING.sm * 3) / 4;
const LeagueCarousel: React.FC = () => {
const renderLeague = (league: League) => {
return (
<TouchableOpacity
key={league.id}
activeOpacity={0.8}
style={[styles.card, { backgroundColor: league.color }]}
>
<View style={styles.cardContent}>
<Text style={styles.leagueIcon}>{league.icon}</Text>
<Text style={styles.leagueName} numberOfLines={2}>
{league.name}
</Text>
</View>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
snapToInterval={CARD_WIDTH + SPACING.sm}
decelerationRate="fast"
>
{LEAGUES.map(renderLeague)}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingVertical: SPACING.md,
},
scrollContent: {
paddingHorizontal: SPACING.md,
gap: SPACING.sm,
},
card: {
width: CARD_WIDTH,
height: CARD_WIDTH * 1.2,
borderRadius: SIZES.radius,
overflow: 'hidden',
},
cardContent: {
flex: 1,
padding: SPACING.sm,
justifyContent: 'space-between',
},
leagueIcon: {
fontSize: 28,
},
leagueName: {
fontSize: 12,
fontWeight: '700',
color: COLORS.text,
lineHeight: 16,
},
});
export default LeagueCarousel;

View File

@ -0,0 +1,233 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { COLORS, SPACING, SIZES } from '../theme/theme';
import { Match } from '../constants/data';
interface LiveMatchCardProps {
match: Match;
}
const LiveMatchCard: React.FC<LiveMatchCardProps> = ({ match }) => {
const [selectedOdd, setSelectedOdd] = React.useState<string | null>(null);
const getStatusColor = () => {
if (match.status.includes('Overtime')) {
return '#FF4D4D';
}
return '#FF4D4D';
};
return (
<View style={styles.card}>
{/* Status Bar */}
<View style={styles.statusRow}>
<View style={styles.statusLeft}>
<View
style={[styles.statusDot, { backgroundColor: getStatusColor() }]}
/>
<Text style={styles.statusText}>{match.status}</Text>
</View>
<TouchableOpacity activeOpacity={0.7}>
<Text style={styles.starIcon}></Text>
</TouchableOpacity>
</View>
{/* Teams */}
<View style={styles.teamsContainer}>
{/* Home Team */}
<View style={styles.teamRow}>
<View style={styles.teamInfo}>
<Text style={styles.teamIcon}>{match.homeTeam.icon}</Text>
<Text style={styles.teamName}>{match.homeTeam.name}</Text>
</View>
{match.homeTeam.score !== undefined && (
<Text
style={[
styles.score,
match.homeTeam.score > (match.awayTeam.score ?? 0)
? styles.scoreHighlight
: null,
]}
>
{match.homeTeam.score}
</Text>
)}
</View>
{/* Away Team */}
<View style={styles.teamRow}>
<View style={styles.teamInfo}>
<Text style={styles.teamIcon}>{match.awayTeam.icon}</Text>
<Text style={styles.teamName}>{match.awayTeam.name}</Text>
</View>
{match.awayTeam.score !== undefined && (
<Text
style={[
styles.score,
match.awayTeam.score > (match.homeTeam.score ?? 0)
? styles.scoreHighlight
: null,
]}
>
{match.awayTeam.score}
</Text>
)}
</View>
</View>
{/* Market Label */}
<View style={styles.marketLabelRow}>
<Text style={styles.marketLabel}>Result</Text>
<View style={styles.dotsRow}>
{Array.from({ length: match.marketCount }).map((_, i) => (
<View key={i} style={[styles.dot, i === 0 && styles.dotActive]} />
))}
</View>
</View>
{/* Odds Buttons */}
<View style={styles.oddsRow}>
{match.markets.map(market => {
const isSelected = selectedOdd === market.label;
return (
<TouchableOpacity
key={market.label}
style={[styles.oddButton, isSelected && styles.oddButtonActive]}
onPress={() => setSelectedOdd(isSelected ? null : market.label)}
activeOpacity={0.7}
>
<Text style={styles.oddLabel}>{market.label}</Text>
<Text
style={[styles.oddValue, isSelected && styles.oddValueActive]}
>
{market.value.toFixed(2)}
</Text>
</TouchableOpacity>
);
})}
</View>
</View>
);
};
const styles = StyleSheet.create({
card: {
backgroundColor: COLORS.surface,
borderRadius: SIZES.radius,
marginHorizontal: SPACING.md,
marginBottom: SPACING.md,
padding: SPACING.md,
},
statusRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: SPACING.sm,
},
statusLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
},
statusDot: {
width: 4,
height: 16,
borderRadius: 2,
},
statusText: {
fontSize: 12,
color: COLORS.textSecondary,
fontWeight: '500',
},
starIcon: {
fontSize: 20,
color: COLORS.textSecondary,
},
teamsContainer: {
gap: 6,
marginBottom: SPACING.sm,
},
teamRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
teamInfo: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
teamIcon: {
fontSize: 16,
},
teamName: {
fontSize: 14,
fontWeight: '600',
color: COLORS.text,
},
score: {
fontSize: 16,
fontWeight: '700',
color: COLORS.textSecondary,
},
scoreHighlight: {
color: '#00D084',
},
marketLabelRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: SPACING.sm,
},
marketLabel: {
fontSize: 12,
color: COLORS.textSecondary,
},
dotsRow: {
flexDirection: 'row',
gap: 4,
},
dot: {
width: 5,
height: 5,
borderRadius: 3,
backgroundColor: COLORS.border,
},
dotActive: {
backgroundColor: COLORS.text,
},
oddsRow: {
flexDirection: 'row',
gap: SPACING.sm,
},
oddButton: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: COLORS.surfaceLight,
borderRadius: 8,
paddingVertical: 10,
paddingHorizontal: 12,
},
oddButtonActive: {
backgroundColor: '#2A4A3A',
borderWidth: 1,
borderColor: '#00D084',
},
oddLabel: {
fontSize: 12,
fontWeight: '500',
color: COLORS.textSecondary,
},
oddValue: {
fontSize: 14,
fontWeight: '700',
color: '#00D084',
},
oddValueActive: {
color: '#00FF9D',
},
});
export default LiveMatchCard;

View File

@ -0,0 +1,118 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
import { Match } from '../constants/data';
import LiveMatchCard from './LiveMatchCard';
interface LiveMatchesSectionProps {
matches: Match[];
}
const LiveMatchesSection: React.FC<LiveMatchesSectionProps> = ({ matches }) => {
// Group matches by league
const grouped = matches.reduce((acc, match) => {
if (!acc[match.league]) {
acc[match.league] = {
icon: match.leagueIcon,
matches: [],
};
}
acc[match.league].matches.push(match);
return acc;
}, {} as Record<string, { icon: string; matches: Match[] }>);
return (
<View style={styles.container}>
{/* Section Header */}
<TouchableOpacity style={styles.sectionHeader} activeOpacity={0.7}>
<View style={styles.headerLeft}>
<View style={styles.liveIndicator}>
<View style={styles.liveDot} />
</View>
<Text style={styles.headerTitle}>Live Matches</Text>
</View>
<Text style={styles.headerArrow}></Text>
</TouchableOpacity>
{/* Grouped Matches */}
{Object.entries(grouped).map(([league, data]) => (
<View key={league}>
{/* League Header */}
<View style={styles.leagueHeader}>
<Text style={styles.leagueIconText}></Text>
<Text style={styles.leagueFlag}>{data.icon}</Text>
<Text style={styles.leagueName}>{league}</Text>
</View>
{/* Match Cards */}
{data.matches.map(match => (
<LiveMatchCard key={match.id} match={match} />
))}
</View>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingBottom: SPACING.xl,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
liveIndicator: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: 'rgba(255, 77, 77, 0.2)',
justifyContent: 'center',
alignItems: 'center',
},
liveDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: '#FF4D4D',
},
headerTitle: {
fontSize: 18,
fontWeight: '700',
color: COLORS.text,
},
headerArrow: {
fontSize: 24,
color: COLORS.text,
fontWeight: '300',
},
leagueHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.sm,
},
leagueIconText: {
fontSize: 14,
color: COLORS.textSecondary,
},
leagueFlag: {
fontSize: 16,
},
leagueName: {
fontSize: 13,
fontWeight: '600',
color: COLORS.textSecondary,
},
});
export default LiveMatchesSection;

View File

@ -0,0 +1,99 @@
import React from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
import { SPORTS, Sport } from '../constants/data';
const SportsHeader: React.FC = () => {
const [activeId, setActiveId] = React.useState('1');
const renderSport = (sport: Sport) => {
const isActive = sport.id === activeId;
return (
<TouchableOpacity
key={sport.id}
style={[styles.sportItem, isActive && styles.sportItemActive]}
onPress={() => setActiveId(sport.id)}
activeOpacity={0.7}
>
<View style={styles.iconRow}>
<Text style={styles.sportIcon}>{sport.icon}</Text>
<Text
style={[styles.sportCount, isActive && styles.sportCountActive]}
>
{sport.count}
</Text>
</View>
<Text
style={[styles.sportName, isActive && styles.sportNameActive]}
numberOfLines={1}
>
{sport.name}
</Text>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{SPORTS.map(renderSport)}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingVertical: SPACING.sm,
borderBottomWidth: 0.5,
borderBottomColor: COLORS.border,
},
scrollContent: {
paddingHorizontal: SPACING.sm,
gap: SPACING.lg,
},
sportItem: {
alignItems: 'center',
paddingVertical: SPACING.xs,
paddingHorizontal: SPACING.xs,
},
sportItemActive: {},
iconRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
marginBottom: 4,
},
sportIcon: {
fontSize: 20,
},
sportCount: {
fontSize: 11,
fontWeight: '600',
color: COLORS.textSecondary,
},
sportCountActive: {
color: COLORS.primary,
},
sportName: {
fontSize: 11,
color: COLORS.textSecondary,
fontWeight: '500',
},
sportNameActive: {
color: COLORS.text,
fontWeight: '700',
},
});
export default SportsHeader;

157
src/constants/data.ts Normal file
View File

@ -0,0 +1,157 @@
// Mock data for the sportsbook app
export interface Sport {
id: string;
name: string;
icon: string;
count: number;
}
export interface League {
id: string;
name: string;
icon: string;
color: string;
secondaryColor: string;
}
export interface Team {
name: string;
icon: string;
score?: number;
}
export interface OddsMarket {
label: string;
value: number;
}
export interface Match {
id: string;
league: string;
leagueIcon: string;
status: string;
homeTeam: Team;
awayTeam: Team;
markets: OddsMarket[];
marketCount: number;
isFavorite: boolean;
}
export const SPORTS: Sport[] = [
{ id: '1', name: 'Football', icon: '⚽', count: 1538 },
{ id: '2', name: 'Basketball', icon: '🏀', count: 280 },
{ id: '3', name: 'Tennis', icon: '🎾', count: 261 },
{ id: '4', name: 'Esports', icon: '🎮', count: 174 },
{ id: '5', name: 'Handball', icon: '🤾', count: 109 },
{ id: '6', name: 'Volleyball', icon: '🏐', count: 134 },
];
export const LEAGUES: League[] = [
{
id: '1',
name: 'UEFA\nChampions...',
icon: '🏆',
color: '#1A3A7A',
secondaryColor: '#0D1F4A',
},
{
id: '2',
name: 'England.\nPremier Le...',
icon: '🦁',
color: '#2D6B3F',
secondaryColor: '#1A4028',
},
{
id: '3',
name: 'Spain.\nLaLiga',
icon: '🏟️',
color: '#C9372C',
secondaryColor: '#8B1A12',
},
{
id: '4',
name: 'Italy. Serie A',
icon: '🇮🇹',
color: '#2E468C',
secondaryColor: '#1A2B5C',
},
{
id: '5',
name: 'ATP 5.\nDoha...',
icon: '🎾',
color: '#3A3A3A',
secondaryColor: '#1A1A1A',
},
];
export const CATEGORY_FILTERS = [
{ id: '1', label: 'Highlights', icon: '🔥', active: true },
{ id: '2', label: '', icon: '⚙️', active: false },
{ id: '3', label: '', icon: '🏀', active: false },
{ id: '4', label: '', icon: '⚽', active: false },
{ id: '5', label: '', icon: '🏇', active: false },
{ id: '6', label: '', icon: '🏐', active: false },
];
export const MATCHES: Match[] = [
{
id: '1',
league: 'Spain. LaLiga',
leagueIcon: '🇪🇸',
status: "1st half 31'",
homeTeam: { name: 'Levante', icon: '⚽', score: 0 },
awayTeam: { name: 'Villarreal', icon: '⚽', score: 0 },
markets: [
{ label: 'W1', value: 4.05 },
{ label: 'X', value: 3 },
{ label: 'W2', value: 2.14 },
],
marketCount: 5,
isFavorite: false,
},
{
id: '2',
league: 'Germany. BBL',
leagueIcon: '🇩🇪',
status: "Overtime 1'",
homeTeam: { name: 'Rostock Seawolves', icon: '🏀', score: 81 },
awayTeam: { name: 'Bamberg', icon: '🏀', score: 85 },
markets: [
{ label: 'W1', value: 4.4 },
{ label: 'W2', value: 1.17 },
],
marketCount: 2,
isFavorite: false,
},
{
id: '3',
league: 'England. Premier League',
leagueIcon: '🏴󠁧󠁢󠁥󠁮󠁧󠁿',
status: "2nd half 67'",
homeTeam: { name: 'Arsenal', icon: '⚽', score: 2 },
awayTeam: { name: 'Chelsea', icon: '⚽', score: 1 },
markets: [
{ label: 'W1', value: 1.35 },
{ label: 'X', value: 5.2 },
{ label: 'W2', value: 8.5 },
],
marketCount: 6,
isFavorite: true,
},
{
id: '4',
league: 'Italy. Serie A',
leagueIcon: '🇮🇹',
status: "1st half 15'",
homeTeam: { name: 'AC Milan', icon: '⚽', score: 0 },
awayTeam: { name: 'Juventus', icon: '⚽', score: 1 },
markets: [
{ label: 'W1', value: 2.8 },
{ label: 'X', value: 3.1 },
{ label: 'W2', value: 2.65 },
],
marketCount: 5,
isFavorite: false,
},
];

View File

@ -0,0 +1,134 @@
import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { COLORS } from '../theme/theme';
import HomeScreen from '../screens/HomeScreen';
import LiveScreen from '../screens/LiveScreen';
import PrematchScreen from '../screens/PrematchScreen';
import MyBetsScreen from '../screens/MyBetsScreen';
import ProfileScreen from '../screens/ProfileScreen';
const Tab = createBottomTabNavigator();
interface TabItemProps {
icon: string;
label: string;
focused: boolean;
}
const TabItem: React.FC<TabItemProps> = ({ icon, label, focused }) => (
<View style={styles.tabItem}>
<Text style={[styles.tabIcon, focused && styles.tabIconActive]}>
{icon}
</Text>
<Text style={[styles.tabLabel, focused && styles.tabLabelActive]}>
{label}
</Text>
</View>
);
const BottomTabNavigator: React.FC = () => {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
tabBarStyle: styles.tabBar,
tabBarShowLabel: false,
tabBarActiveTintColor: '#FFD700',
tabBarInactiveTintColor: COLORS.textSecondary,
}}
>
<Tab.Screen
name="Menu"
component={HomeScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="☰" label="Menu" focused={focused} />
),
}}
/>
<Tab.Screen
name="Main"
component={HomeScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="⚡" label="Main" focused={focused} />
),
}}
/>
<Tab.Screen
name="Live"
component={LiveScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="📺" label="Live" focused={focused} />
),
}}
/>
<Tab.Screen
name="Prematch"
component={PrematchScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="⚽" label="Prematch" focused={focused} />
),
}}
/>
<Tab.Screen
name="MyBets"
component={MyBetsScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="🎟️" label="My Bets" focused={focused} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabItem icon="👤" label="Log in" focused={focused} />
),
}}
/>
</Tab.Navigator>
);
};
const styles = StyleSheet.create({
tabBar: {
backgroundColor: COLORS.surface,
borderTopWidth: 0.5,
borderTopColor: COLORS.border,
height: 70,
paddingTop: 8,
paddingBottom: 12,
elevation: 0,
shadowOpacity: 0,
},
tabItem: {
alignItems: 'center',
justifyContent: 'center',
gap: 2,
},
tabIcon: {
fontSize: 20,
opacity: 0.5,
},
tabIconActive: {
opacity: 1,
},
tabLabel: {
fontSize: 10,
fontWeight: '500',
color: COLORS.textSecondary,
},
tabLabelActive: {
color: '#FFD700',
fontWeight: '700',
},
});
export default BottomTabNavigator;

View File

@ -0,0 +1,76 @@
import React from 'react';
import {
View,
ScrollView,
StyleSheet,
StatusBar,
Text,
TouchableOpacity,
} from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
import SportsHeader from '../components/SportsHeader';
import LeagueCarousel from '../components/LeagueCarousel';
import CategoryPills from '../components/CategoryPills';
import LiveMatchesSection from '../components/LiveMatchesSection';
import { MATCHES } from '../constants/data';
const HomeScreen: React.FC = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Sportsbook</Text>
<TouchableOpacity activeOpacity={0.7}>
<Text style={styles.searchIcon}>🔍</Text>
</TouchableOpacity>
</View>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{/* Sports Categories */}
<SportsHeader />
{/* League Carousel */}
<LeagueCarousel />
{/* Live Matches Header + Category Filters */}
<CategoryPills />
{/* Live Matches Section */}
<LiveMatchesSection matches={MATCHES} />
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
paddingTop: SPACING.xl,
},
headerTitle: {
fontSize: 22,
fontWeight: '700',
color: COLORS.text,
},
searchIcon: {
fontSize: 20,
},
scrollContent: {
paddingBottom: 100,
},
});
export default HomeScreen;

View File

@ -0,0 +1,74 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
const LiveScreen: React.FC = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
<View style={styles.header}>
<Text style={styles.headerTitle}>Live</Text>
</View>
<ScrollView
contentContainerStyle={styles.content}
showsVerticalScrollIndicator={false}
>
<View style={styles.emptyState}>
<Text style={styles.emptyIcon}>🔴</Text>
<Text style={styles.emptyTitle}>Live Events</Text>
<Text style={styles.emptySubtitle}>
All live matches and events will appear here in real-time.
</Text>
</View>
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
paddingTop: SPACING.xl,
borderBottomWidth: 0.5,
borderBottomColor: COLORS.border,
},
headerTitle: {
fontSize: 22,
fontWeight: '700',
color: COLORS.text,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 100,
},
emptyState: {
alignItems: 'center',
paddingHorizontal: SPACING.xl,
paddingTop: 80,
},
emptyIcon: {
fontSize: 48,
marginBottom: SPACING.md,
},
emptyTitle: {
fontSize: 20,
fontWeight: '700',
color: COLORS.text,
marginBottom: SPACING.sm,
},
emptySubtitle: {
fontSize: 14,
color: COLORS.textSecondary,
textAlign: 'center',
lineHeight: 20,
},
});
export default LiveScreen;

View File

@ -0,0 +1,75 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
const MyBetsScreen: React.FC = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
<View style={styles.header}>
<Text style={styles.headerTitle}>My Bets</Text>
</View>
<ScrollView
contentContainerStyle={styles.content}
showsVerticalScrollIndicator={false}
>
<View style={styles.emptyState}>
<Text style={styles.emptyIcon}>🎟</Text>
<Text style={styles.emptyTitle}>No Active Bets</Text>
<Text style={styles.emptySubtitle}>
Your active and settled bets will appear here. Start betting on live
or upcoming matches!
</Text>
</View>
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
paddingTop: SPACING.xl,
borderBottomWidth: 0.5,
borderBottomColor: COLORS.border,
},
headerTitle: {
fontSize: 22,
fontWeight: '700',
color: COLORS.text,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 100,
},
emptyState: {
alignItems: 'center',
paddingHorizontal: SPACING.xl,
paddingTop: 80,
},
emptyIcon: {
fontSize: 48,
marginBottom: SPACING.md,
},
emptyTitle: {
fontSize: 20,
fontWeight: '700',
color: COLORS.text,
marginBottom: SPACING.sm,
},
emptySubtitle: {
fontSize: 14,
color: COLORS.textSecondary,
textAlign: 'center',
lineHeight: 20,
},
});
export default MyBetsScreen;

View File

@ -0,0 +1,74 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
import { COLORS, SPACING } from '../theme/theme';
const PrematchScreen: React.FC = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
<View style={styles.header}>
<Text style={styles.headerTitle}>Prematch</Text>
</View>
<ScrollView
contentContainerStyle={styles.content}
showsVerticalScrollIndicator={false}
>
<View style={styles.emptyState}>
<Text style={styles.emptyIcon}>📅</Text>
<Text style={styles.emptyTitle}>Upcoming Events</Text>
<Text style={styles.emptySubtitle}>
Browse upcoming matches and place your bets before they start.
</Text>
</View>
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
paddingTop: SPACING.xl,
borderBottomWidth: 0.5,
borderBottomColor: COLORS.border,
},
headerTitle: {
fontSize: 22,
fontWeight: '700',
color: COLORS.text,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 100,
},
emptyState: {
alignItems: 'center',
paddingHorizontal: SPACING.xl,
paddingTop: 80,
},
emptyIcon: {
fontSize: 48,
marginBottom: SPACING.md,
},
emptyTitle: {
fontSize: 20,
fontWeight: '700',
color: COLORS.text,
marginBottom: SPACING.sm,
},
emptySubtitle: {
fontSize: 14,
color: COLORS.textSecondary,
textAlign: 'center',
lineHeight: 20,
},
});
export default PrematchScreen;

View File

@ -0,0 +1,114 @@
import React from 'react';
import {
View,
Text,
StyleSheet,
StatusBar,
TouchableOpacity,
} from 'react-native';
import { COLORS, SPACING, SIZES } from '../theme/theme';
const ProfileScreen: React.FC = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
<View style={styles.header}>
<Text style={styles.headerTitle}>Account</Text>
</View>
<View style={styles.content}>
<View style={styles.avatarContainer}>
<Text style={styles.avatarText}>👤</Text>
</View>
<Text style={styles.loginTitle}>Log in to your account</Text>
<Text style={styles.loginSubtitle}>
Access your bets, wallet, and personalized settings.
</Text>
<TouchableOpacity style={styles.loginButton} activeOpacity={0.8}>
<Text style={styles.loginButtonText}>Log In</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.registerButton} activeOpacity={0.8}>
<Text style={styles.registerButtonText}>Register</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.md,
paddingTop: SPACING.xl,
borderBottomWidth: 0.5,
borderBottomColor: COLORS.border,
},
headerTitle: {
fontSize: 22,
fontWeight: '700',
color: COLORS.text,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: SPACING.xl,
paddingBottom: 100,
},
avatarContainer: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: COLORS.surface,
justifyContent: 'center',
alignItems: 'center',
marginBottom: SPACING.lg,
},
avatarText: {
fontSize: 36,
},
loginTitle: {
fontSize: 20,
fontWeight: '700',
color: COLORS.text,
marginBottom: SPACING.sm,
},
loginSubtitle: {
fontSize: 14,
color: COLORS.textSecondary,
textAlign: 'center',
lineHeight: 20,
marginBottom: SPACING.lg,
},
loginButton: {
backgroundColor: '#FFD700',
borderRadius: SIZES.radius,
paddingVertical: 14,
alignItems: 'center',
width: '100%',
marginBottom: SPACING.sm,
},
loginButtonText: {
fontSize: 16,
fontWeight: '700',
color: '#000',
},
registerButton: {
borderWidth: 1,
borderColor: COLORS.border,
borderRadius: SIZES.radius,
paddingVertical: 14,
alignItems: 'center',
width: '100%',
},
registerButtonText: {
fontSize: 16,
fontWeight: '600',
color: COLORS.text,
},
});
export default ProfileScreen;

27
src/theme/theme.ts Normal file
View File

@ -0,0 +1,27 @@
export const COLORS = {
background: '#0D0D0D',
surface: '#1A1A1A',
surfaceLight: '#262626',
primary: '#FFD700', // Gold/Yellow for odds/highlights
text: '#FFFFFF',
textSecondary: '#A0A0A0',
accentGreen: '#00D084', // Premier League
accentBlue: '#007AFF', // UEFA
accentRed: '#FF4D4D', // LaLiga
accentIndigo: '#4C6EF5', // Serie A
accentOrange: '#FF8C00', // Bundesliga
border: '#333333',
};
export const SPACING = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
};
export const SIZES = {
radius: 12,
pillRadius: 20,
};