diff --git a/frontend-challenge/court-review-app/.gitignore b/frontend-challenge/court-review-app/.gitignore new file mode 100644 index 000000000..f8c6c2e83 --- /dev/null +++ b/frontend-challenge/court-review-app/.gitignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/frontend-challenge/court-review-app/.vscode/extensions.json b/frontend-challenge/court-review-app/.vscode/extensions.json new file mode 100644 index 000000000..b7ed83779 --- /dev/null +++ b/frontend-challenge/court-review-app/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/frontend-challenge/court-review-app/.vscode/settings.json b/frontend-challenge/court-review-app/.vscode/settings.json new file mode 100644 index 000000000..e2798e426 --- /dev/null +++ b/frontend-challenge/court-review-app/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/frontend-challenge/court-review-app/App.js b/frontend-challenge/court-review-app/App.js new file mode 100644 index 000000000..104cf5085 --- /dev/null +++ b/frontend-challenge/court-review-app/App.js @@ -0,0 +1,19 @@ +import 'react-native-gesture-handler'; +import * as React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import CourtList from './components/CourtList'; +import CourtDetail from './components/CourtDetail'; + +const Stack = createStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} diff --git a/frontend-challenge/court-review-app/README.md b/frontend-challenge/court-review-app/README.md new file mode 100644 index 000000000..48dd63ff3 --- /dev/null +++ b/frontend-challenge/court-review-app/README.md @@ -0,0 +1,50 @@ +# Welcome to your Expo app 👋 + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/frontend-challenge/court-review-app/app.json b/frontend-challenge/court-review-app/app.json new file mode 100644 index 000000000..b047cde68 --- /dev/null +++ b/frontend-challenge/court-review-app/app.json @@ -0,0 +1,49 @@ +{ + "expo": { + "name": "court-review-app", + "slug": "court-review-app", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "courtreviewapp", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "backgroundColor": "#000000" + } + } + ], + "expo-font" + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } +} diff --git a/frontend-challenge/court-review-app/app/_layout.tsx b/frontend-challenge/court-review-app/app/_layout.tsx new file mode 100644 index 000000000..d2a8b0bca --- /dev/null +++ b/frontend-challenge/court-review-app/app/_layout.tsx @@ -0,0 +1,5 @@ +import { Stack } from "expo-router"; + +export default function RootLayout() { + return ; +} diff --git a/frontend-challenge/court-review-app/app/index.tsx b/frontend-challenge/court-review-app/app/index.tsx new file mode 100644 index 000000000..acf3ac3c3 --- /dev/null +++ b/frontend-challenge/court-review-app/app/index.tsx @@ -0,0 +1,171 @@ +import { PlayfairDisplay_700Bold, PlayfairDisplay_700Bold_Italic, useFonts } from '@expo-google-fonts/playfair-display'; +import { Ubuntu_700Bold } from "@expo-google-fonts/ubuntu"; +import React from 'react'; +import { Button, FlatList, ImageBackground, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { courts } from '../data/courtsMock'; + +const tennisGreen = '#586545'; +const tennisBrown = '#8B5736'; + +type Review = { + reviewer: string; + stars: string; + comment: string; +}; + +type Court = { + id: number; + name: string; + location: string; + surfaceType: string; + rating: number; + reviews: Review[]; +}; + +export default function HomeScreen() { + let [fontsLoaded] = useFonts({ Ubuntu_700Bold, PlayfairDisplay_700Bold, PlayfairDisplay_700Bold_Italic}); + + + const [search, setSearch] = React.useState(''); + const [selectedCourt, setSelectedCourt] = React.useState(null); + const [reviewer, setReviewer] = React.useState(''); + const [stars, setStars] = React.useState('5'); + const [comment, setComment] = React.useState(''); + const [reviews, setReviews] = React.useState([]); + + React.useEffect(() => { + setReviews(selectedCourt?.reviews || []); + }, [selectedCourt]); + + const filteredCourts = courts.filter( + c => c.name.toLowerCase().includes(search.toLowerCase()) || + c.location.toLowerCase().includes(search.toLowerCase()) + ); + + const handleAddReview = () => { + if (!reviewer || !comment) return; + const newReview: Review = { reviewer, stars, comment }; + setReviews([...reviews, newReview]); + setReviewer(''); + setStars('5'); + setComment(''); + }; + + if (!fontsLoaded) { + return Loading fonts...; + } + + const bg = require('../assets/images/bg.jpg'); + if (!selectedCourt) { + return ( + + + + tennis + club + + + item.id.toString()} + renderItem={({ item }) => ( + setSelectedCourt(item)} + > + + {item.name} + + + + {item.location} | {item.surfaceType} | ⭐ {item.rating} + + + )} + /> + + + ); + } + + return ( + + + setSelectedCourt(null)} style={styles.backButton}> + {'< Back to list'} + + + tennis + club + + {selectedCourt.name} + + {selectedCourt.location} | {selectedCourt.surfaceType} | ⭐ {selectedCourt.rating} + + Reviews + idx.toString()} + renderItem={({ item }) => ( + + {item.reviewer}: {item.stars}★ + {item.comment} + + )} + ListEmptyComponent={No reviews yet.} + /> + Add a Review + + + +