Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions frontend-challenge/tennis-court/.gitignore
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions frontend-challenge/tennis-court/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "recommendations": ["expo.vscode-expo-tools"] }
7 changes: 7 additions & 0 deletions frontend-challenge/tennis-court/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit",
"source.sortMembers": "explicit"
}
}
48 changes: 48 additions & 0 deletions frontend-challenge/tennis-court/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# BYOB Tennis Court

This is a small, mobile-first mock app for browsing tennis courts, viewing court details, searching, paginating results, and leaving reviews.

# Libraries used

- Expo / React Native (managed workflow)
- React (UI)
- TypeScript (static types)
- Expo Router / file-based routing (project structure uses app/ routes)
- Basic UI components (local components in the repo)

# Mock data

- Courts and reviews are mocked in: [assets/tennis_courts_mock.json](assets/tennis_courts_mock.json)

# Components

- [`CourtCard`](components/CourtCard.tsx) — card used to display a court in lists
- [`Header`](components/Header.tsx) — app header and navigation affordance
- [`Pagination`](components/Pagination.tsx) — simple pager control for lists
- [`SearchBar`](components/SearchBar.tsx) — search input for filtering courts

Screens (app routes):

- [app/index.tsx](app/index.tsx) — main list entry
- [app/\_layout.tsx](app/_layout.tsx) — global layout / router shell
- [app/courts/[id].tsx](app/courts/[id].tsx) — court detail screen

# Inspiration

UI and interaction were inspired by short-term rental and review patterns (similar to Airbnb): clear listing cards, prominent photos, search, and review flows.

# How to run this project

1. Install dependencies:
```sh
npm install
```
2. Start the Expo dev server:
```sh
npm start
```
or
```sh
npx expo start
```
3. Open the app on a simulator or physical device using the Expo DevTools.
48 changes: 48 additions & 0 deletions frontend-challenge/tennis-court/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"expo": {
"name": "tennis-court",
"slug": "tennis-court",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/tennis.png",
"scheme": "tenniscourt",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/tennis.png",
"backgroundImage": "./assets/images/tennis.png",
"monochromeImage": "./assets/images/tennis.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"output": "static",
"favicon": "./assets/images/tennis.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/tennis.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}
39 changes: 39 additions & 0 deletions frontend-challenge/tennis-court/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Stack } from "expo-router";
import Toast, { BaseToast, ToastConfig } from "react-native-toast-message";

const AIRBNB_RED = "#FF5A5F";
const DARK_GREY = "#222";

const toastConfig: ToastConfig = {
success: (props) => (
<BaseToast
{...props}
style={{
borderLeftColor: AIRBNB_RED,
backgroundColor: "#FFF",
borderRadius: 12,
borderLeftWidth: 6,
paddingVertical: 8,
}}
contentContainerStyle={{ paddingHorizontal: 12 }}
text1Style={{
fontSize: 15,
fontWeight: "700",
color: AIRBNB_RED,
}}
text2Style={{
fontSize: 13,
color: DARK_GREY,
}}
/>
),
};

export default function Layout() {
return (
<>
<Stack screenOptions={{ headerShown: false }} />
<Toast config={toastConfig} />
</>
);
}
25 changes: 25 additions & 0 deletions frontend-challenge/tennis-court/app/courts/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Text } from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router";
import CourtDetailScreen from "@/screens/CourtDetailScreen";
import courtsData from "@/assets/tennis_courts_mock.json";
import type { Court } from "@/types/court";
export default function CourtDetailRoute() {
const params = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const id = params.id;

const court: Court | undefined = courtsData.courts.find(
(c) => c.id.toString() === id
);

if (!court) return <Text>Court not found</Text>;

return (
<CourtDetailScreen
court={court}
onBack={() => {
router.back();
}}
/>
);
}
5 changes: 5 additions & 0 deletions frontend-challenge/tennis-court/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import CourtsListScreen from "../screens/CourtsListScreen";

export default function App() {
return <CourtsListScreen />;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading