first commit
This commit is contained in:
parent
3058e27685
commit
0503230fd4
48
App.tsx
48
App.tsx
|
|
@ -1,45 +1,13 @@
|
|||
/**
|
||||
* Sample React Native App
|
||||
* https://github.com/facebook/react-native
|
||||
*
|
||||
* @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';
|
||||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import BottomTabNavigator from './src/navigation/BottomTabNavigator';
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<AppContent />
|
||||
</SafeAreaProvider>
|
||||
<NavigationContainer>
|
||||
<BottomTabNavigator />
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
2297
ios/Podfile.lock
Normal file
2297
ios/Podfile.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -9,6 +9,7 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
0C80B921A6F3F58F76C31292 /* libPods-fortune.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-fortune.a */; };
|
||||
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 */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
|
@ -159,6 +160,7 @@
|
|||
files = (
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
4F08206F5132F769BA13651A /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -370,6 +372,10 @@
|
|||
);
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
|
|
@ -377,8 +383,13 @@
|
|||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -435,6 +446,10 @@
|
|||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
|
|
@ -442,8 +457,12 @@
|
|||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
|
|
|||
10
ios/fortune.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
ios/fortune.xcworkspace/contents.xcworkspacedata
generated
Normal 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>
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
|
|
@ -36,6 +35,8 @@
|
|||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
|
|
|||
12342
package-lock.json
generated
Normal file
12342
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
|
@ -10,10 +10,18 @@
|
|||
"test": "jest"
|
||||
},
|
||||
"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-native": "0.84.0",
|
||||
"@react-native/new-app-screen": "0.84.0",
|
||||
"react-native-safe-area-context": "^5.5.2"
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"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": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
|
@ -38,4 +46,4 @@
|
|||
"engines": {
|
||||
"node": ">= 22.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
src/components/CategoryPills.tsx
Normal file
80
src/components/CategoryPills.tsx
Normal 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;
|
||||
79
src/components/LeagueCarousel.tsx
Normal file
79
src/components/LeagueCarousel.tsx
Normal 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;
|
||||
233
src/components/LiveMatchCard.tsx
Normal file
233
src/components/LiveMatchCard.tsx
Normal 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;
|
||||
118
src/components/LiveMatchesSection.tsx
Normal file
118
src/components/LiveMatchesSection.tsx
Normal 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;
|
||||
99
src/components/SportsHeader.tsx
Normal file
99
src/components/SportsHeader.tsx
Normal 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
157
src/constants/data.ts
Normal 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,
|
||||
},
|
||||
];
|
||||
134
src/navigation/BottomTabNavigator.tsx
Normal file
134
src/navigation/BottomTabNavigator.tsx
Normal 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;
|
||||
76
src/screens/HomeScreen.tsx
Normal file
76
src/screens/HomeScreen.tsx
Normal 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;
|
||||
74
src/screens/LiveScreen.tsx
Normal file
74
src/screens/LiveScreen.tsx
Normal 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;
|
||||
75
src/screens/MyBetsScreen.tsx
Normal file
75
src/screens/MyBetsScreen.tsx
Normal 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;
|
||||
74
src/screens/PrematchScreen.tsx
Normal file
74
src/screens/PrematchScreen.tsx
Normal 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;
|
||||
114
src/screens/ProfileScreen.tsx
Normal file
114
src/screens/ProfileScreen.tsx
Normal 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
27
src/theme/theme.ts
Normal 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,
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user