# uber **Repository Path**: ocisly/uber ## Basic Information - **Project Name**: uber - **Description**: uber clone - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-10-01 - **Last Updated**: 2024-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

Project Banner
reactnative postgresql expo stripe

Full Stack Uber Clone

Build this project step by step with our detailed tutorial on JavaScript Mastery YouTube. Join the JSM family!
## πŸ“‹ Table of Contents 1. πŸ€– [Introduction](#introduction) 2. βš™οΈ [Tech Stack](#tech-stack) 3. πŸ”‹ [Features](#features) 4. 🀸 [Quick Start](#quick-start) 5. πŸ•ΈοΈ [Snippets (Code to Copy)](#snippets) 6. πŸ–‡οΈ [Links](#links) 7. πŸ“¦ [Assets](#assets) 8. πŸš€ [More](#more) ## 🚨 Tutorial This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, JavaScript Mastery. If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner! ## πŸ€– Introduction Built with React Native for handling the user interface, Google Maps for rendering maps with directions, stripe for handling payments, serverless Postgres for managing databases, and styled with TailwindCSS, Uber Clone is a perfect mobile app. The primary goal is to demonstrate how to develop full-stack mobile applications to showcase the developer's skills in a unique manner that creates a lasting impact. If you're getting started and need assistance or face any bugs, join our active Discord community with over **34k+** members. It's a place where people help each other out. ## βš™οΈ Tech Stack - React Native - Expo - Stripe - PostgreSQL - Google Maps - zustand - Clerk - Tailwind CSS ## πŸ”‹ Features πŸ‘‰ **Onboarding Flow**: Seamless user registration and setup process. πŸ‘‰ **Email Password Authentication with Verification**: Secure login with email verification. πŸ‘‰ **oAuth Using Google**: Easy login using Google credentials. πŸ‘‰ **Authorization**: Secure access control for different user roles. πŸ‘‰ **Home Screen with Live Location & Google Map**: Real-time location tracking with markers on a map. πŸ‘‰ **Recent Rides**: View a list of recent rides at a glance. πŸ‘‰ **Google Places Autocomplete**: Search any place on Earth with autocomplete suggestions. πŸ‘‰ **Find Rides**: Search for rides by entering 'From' and 'To' locations. πŸ‘‰ **Select Rides from Map**: Choose available cars near your location from the map. πŸ‘‰ **Confirm Ride with Detailed Information**: View complete ride details, including time and fare price. πŸ‘‰ **Pay for Ride Using Stripe**: Make payments using multiple methods like cards and others. πŸ‘‰ **Create Rides After Successful Payment**: Book a ride after confirming payment. πŸ‘‰ **Profile**: Manage account details in the profile screen. πŸ‘‰ **History**: Review all rides booked so far. πŸ‘‰ **Responsive on Android and iOS**: Optimized for both Android and iOS devices. and many more, including code architecture and reusability ## 🀸 Quick Start Follow these steps to set up the project locally on your machine. **Prerequisites** Make sure you have the following installed on your machine: - [Git](https://git-scm.com/) - [Node.js](https://nodejs.org/en) - [npm](https://www.npmjs.com/) (Node Package Manager) **Cloning the Repository** ```bash git clone https://github.com/JavaScript-Mastery-Pro/uber.git cd uber ``` **Installation** Install the project dependencies using npm: ```bash npm install ``` **Set Up Environment Variables** Create a new file named `.env` in the root of your project and add the following content: ```env EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY= EXPO_PUBLIC_PLACES_API_KEY= EXPO_PUBLIC_DIRECTIONS_API_KEY= DATABASE_URL= EXPO_PUBLIC_SERVER_URL=https://uber.dev/ EXPO_PUBLIC_GEOAPIFY_API_KEY= EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY= STRIPE_SECRET_KEY= ``` Replace the placeholder values with your actual Clerk, Stripe, NeonDB, Google Maps, andgeoapify credentials. You can obtain these credentials by signing up on the [Clerk](https://clerk.com/), [Stripe](https://stripe.com/in), [NeonDB](https://neon.tech/), [Google Maps](https://console.cloud.google.com/) and [geoapify](https://www.geoapify.com/) websites respectively. **Running the Project** ```bash npx expo start ``` Download the [Expo Go](https://expo.dev/go) app and Scan the QR code on your respective device to view the project. ## πŸ•ΈοΈ Snippets Here are some code snippets from the project to help you get started quickly. ### Setup
.vscode/settings.json ```json { "editor.formatOnPaste": true, "editor.formatOnSave": true, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } } ```
tailwind.config.js ```js /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"], theme: { extend: { fontFamily: { Jakarta: ["Jakarta", "sans-serif"], JakartaBold: ["Jakarta-Bold", "sans-serif"], JakartaExtraBold: ["Jakarta-ExtraBold", "sans-serif"], JakartaExtraLight: ["Jakarta-ExtraLight", "sans-serif"], JakartaLight: ["Jakarta-Light", "sans-serif"], JakartaMedium: ["Jakarta-Medium", "sans-serif"], JakartaSemiBold: ["Jakarta-SemiBold", "sans-serif"], }, colors: { primary: { 100: "#F5F8FF", 200: "#EBF4FF", 300: "#C3D9FF", 400: "#9BBFFF", 500: "#0286FF", 600: "#6A85E6", 700: "#475A99", 800: "#364573", 900: "#242B4D", }, secondary: { 100: "#F8F8F8", 200: "#F1F1F1", 300: "#D9D9D9", 400: "#C2C2C2", 500: "#AAAAAA", 600: "#999999", 700: "#666666", 800: "#4D4D4D", 900: "#333333", }, success: { 100: "#F0FFF4", 200: "#C6F6D5", 300: "#9AE6B4", 400: "#68D391", 500: "#38A169", 600: "#2F855A", 700: "#276749", 800: "#22543D", 900: "#1C4532", }, danger: { 100: "#FFF5F5", 200: "#FED7D7", 300: "#FEB2B2", 400: "#FC8181", 500: "#F56565", 600: "#E53E3E", 700: "#C53030", 800: "#9B2C2C", 900: "#742A2A", }, warning: { 100: "#FFFBEB", 200: "#FEF3C7", 300: "#FDE68A", 400: "#FACC15", 500: "#EAB308", 600: "#CA8A04", 700: "#A16207", 800: "#854D0E", 900: "#713F12", }, general: { 100: "#CED1DD", 200: "#858585", 300: "#EEEEEE", 400: "#0CC25F", 500: "#F6F8FA", 600: "#E6F3FF", 700: "#EBEBEB", 800: "#ADADAD", }, }, }, }, plugins: [], }; ```
types/type.d.ts ```ts import {TextInputProps, TouchableOpacityProps} from "react-native"; declare interface Driver { driver_id: number; first_name: string; last_name: string; profile_image_url: string; car_image_url: string; car_seats: number; rating: number; } declare interface MarkerData { latitude: number; longitude: number; id: number; title: string; profile_image_url: string; car_image_url: string; car_seats: number; rating: number; first_name: string; last_name: string; time?: number; price?: string; } declare interface MapProps { destinationLatitude?: number; destinationLongitude?: number; onDriverTimesCalculated?: (driversWithTimes: MarkerData[]) => void; selectedDriver?: number | null; onMapReady?: () => void; } declare interface Ride { origin_address: string; destination_address: string; origin_latitude: number; origin_longitude: number; destination_latitude: number; destination_longitude: number; ride_time: number; fare_price: number; payment_status: string; driver_id: number; user_email: string; created_at: string; driver: { first_name: string; last_name: string; car_seats: number; }; } declare interface ButtonProps extends TouchableOpacityProps { title: string; bgVariant?: "primary" | "secondary" | "danger" | "outline" | "success"; textVariant?: "primary" | "default" | "secondary" | "danger" | "success"; IconLeft?: React.ComponentType; IconRight?: React.ComponentType; className?: string; } declare interface GoogleInputProps { icon?: string; initialLocation?: string; containerStyle?: string; textInputBackgroundColor?: string; handlePress: ({ latitude, longitude, address, }: { latitude: number; longitude: number; address: string; }) => void; } declare interface InputFieldProps extends TextInputProps { label: string; icon?: any; secureTextEntry?: boolean; labelStyle?: string; containerStyle?: string; inputStyle?: string; iconStyle?: string; className?: string; } declare interface PaymentProps { fullName: string; email: string; amount: string; driverId: number; rideTime: number; } declare interface LocationStore { userLatitude: number | null; userLongitude: number | null; userAddress: string | null; destinationLatitude: number | null; destinationLongitude: number | null; destinationAddress: string | null; setUserLocation: ({ latitude, longitude, address, }: { latitude: number; longitude: number; address: string; }) => void; setDestinationLocation: ({ latitude, longitude, address, }: { latitude: number; longitude: number; address: string; }) => void; } declare interface DriverStore { drivers: MarkerData[]; selectedDriver: number | null; setSelectedDriver: (driverId: number) => void; setDrivers: (drivers: MarkerData[]) => void; clearSelectedDriver: () => void; } declare interface DriverCardProps { item: MarkerData; selected: number; setSelected: () => void; } ```
types/image.d.ts ```ts declare module "*.png" { const value: any; export default value; } declare module "*.jpg" { const value: any; export default value; } declare module "*.jpeg" { const value: any; export default value; } declare module "*.gif" { const value: any; export default value; } declare module "*.svg" { const value: any; export default value; } ```
constants/index.ts ```ts import arrowDown from "@/assets/icons/arrow-down.png"; import arrowUp from "@/assets/icons/arrow-up.png"; import backArrow from "@/assets/icons/back-arrow.png"; import chat from "@/assets/icons/chat.png"; import checkmark from "@/assets/icons/check.png"; import close from "@/assets/icons/close.png"; import dollar from "@/assets/icons/dollar.png"; import email from "@/assets/icons/email.png"; import eyecross from "@/assets/icons/eyecross.png"; import google from "@/assets/icons/google.png"; import home from "@/assets/icons/home.png"; import list from "@/assets/icons/list.png"; import lock from "@/assets/icons/lock.png"; import map from "@/assets/icons/map.png"; import marker from "@/assets/icons/marker.png"; import out from "@/assets/icons/out.png"; import person from "@/assets/icons/person.png"; import pin from "@/assets/icons/pin.png"; import point from "@/assets/icons/point.png"; import profile from "@/assets/icons/profile.png"; import search from "@/assets/icons/search.png"; import selectedMarker from "@/assets/icons/selected-marker.png"; import star from "@/assets/icons/star.png"; import target from "@/assets/icons/target.png"; import to from "@/assets/icons/to.png"; import check from "@/assets/images/check.png"; import getStarted from "@/assets/images/get-started.png"; import message from "@/assets/images/message.png"; import noResult from "@/assets/images/no-result.png"; import onboarding1 from "@/assets/images/onboarding1.png"; import onboarding2 from "@/assets/images/onboarding2.png"; import onboarding3 from "@/assets/images/onboarding3.png"; import signUpCar from "@/assets/images/signup-car.png"; export const images = { onboarding1, onboarding2, onboarding3, getStarted, signUpCar, check, noResult, message, }; export const icons = { arrowDown, arrowUp, backArrow, chat, checkmark, close, dollar, email, eyecross, google, home, list, lock, map, marker, out, person, pin, point, profile, search, selectedMarker, star, target, to, }; export const onboarding = [ { id: 1, title: "The perfect ride is just a tap away!", description: "Your journey begins with Ryde. Find your ideal ride effortlessly.", image: images.onboarding1, }, { id: 2, title: "Best car in your hands with Ryde", description: "Discover the convenience of finding your perfect ride with Ryde", image: images.onboarding2, }, { id: 3, title: "Your ride, your way. Let's go!", description: "Enter your destination, sit back, and let us take care of the rest.", image: images.onboarding3, }, ]; export const data = { onboarding, }; ```
Root Layout Fonts ```ts const [loaded] = useFonts({ "Jakarta-Bold": require("../assets/fonts/PlusJakartaSans-Bold.ttf"), "Jakarta-ExtraBold": require("../assets/fonts/PlusJakartaSans-ExtraBold.ttf"), "Jakarta-ExtraLight": require("../assets/fonts/PlusJakartaSans-ExtraLight.ttf"), "Jakarta-Light": require("../assets/fonts/PlusJakartaSans-Light.ttf"), "Jakarta-Medium": require("../assets/fonts/PlusJakartaSans-Medium.ttf"), "Jakarta-Regular": require("../assets/fonts/PlusJakartaSans-Regular.ttf"), "Jakarta-SemiBold": require("../assets/fonts/PlusJakartaSans-SemiBold.ttf"), }); ```
### Components
components/CustomButton ```tsx import {TouchableOpacity, Text} from "react-native"; import {ButtonProps} from "@/types/type"; const getBgVariantStyle = (variant: ButtonProps["bgVariant"]) => { switch (variant) { case "secondary": return "bg-gray-500"; case "danger": return "bg-red-500"; case "success": return "bg-green-500"; case "outline": return "bg-transparent border-neutral-300 border-[0.5px]"; default: return "bg-[#0286FF]"; } }; const getTextVariantStyle = (variant: ButtonProps["textVariant"]) => { switch (variant) { case "primary": return "text-black"; case "secondary": return "text-gray-100"; case "danger": return "text-red-100"; case "success": return "text-green-100"; default: return "text-white"; } }; const CustomButton = ({ onPress, title, bgVariant = "primary", textVariant = "default", IconLeft, IconRight, className, ...props }: ButtonProps) => { return ( {IconLeft && } {title} {IconRight && } ); }; export default CustomButton; ```
components/InputField ```tsx import { TextInput, View, Text, Image, KeyboardAvoidingView, TouchableWithoutFeedback, Keyboard, Platform, } from "react-native"; import {InputFieldProps} from "@/types/type"; const InputField = ({ label, icon, secureTextEntry = false, labelStyle, containerStyle, inputStyle, iconStyle, className, ...props }: InputFieldProps) => { return ( {label} {icon && ( )} ); }; export default InputField; ```
components/DriverCard.tsx ```tsx import React from "react"; import {Image, Text, TouchableOpacity, View} from "react-native"; import {icons} from "@/constants"; import {formatTime} from "@/lib/utils"; import {DriverCardProps} from "@/types/type"; const DriverCard = ({item, selected, setSelected}: DriverCardProps) => { return ( {item.title} 4 ${item.price} | {formatTime(item.time!)} | {item.car_seats} seats ); }; export default DriverCard; ```
### Utilities
lib/fetch.ts ```ts import {useState, useEffect, useCallback} from "react"; export const fetchAPI = async (url: string, options?: RequestInit) => { try { const response = await fetch(url, options); if (!response.ok) { new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error("Fetch error:", error); throw error; } }; export const useFetch = (url: string, options?: RequestInit) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const result = await fetchAPI(url, options); setData(result.data); } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }, [url, options]); useEffect(() => { fetchData(); }, [fetchData]); return {data, loading, error, refetch: fetchData}; }; ```
lib/map.ts ```ts import {Driver, MarkerData} from "@/types/type"; const directionsAPI = process.env.EXPO_PUBLIC_GOOGLE_API_KEY; export const generateMarkersFromData = ({ data, userLatitude, userLongitude, }: { data: Driver[]; userLatitude: number; userLongitude: number; }): MarkerData[] => { return data.map((driver) => { const latOffset = (Math.random() - 0.5) * 0.01; // Random offset between -0.005 and 0.005 const lngOffset = (Math.random() - 0.5) * 0.01; // Random offset between -0.005 and 0.005 return { latitude: userLatitude + latOffset, longitude: userLongitude + lngOffset, title: `${driver.first_name} ${driver.last_name}`, ...driver, }; }); }; export const calculateRegion = ({ userLatitude, userLongitude, destinationLatitude, destinationLongitude, }: { userLatitude: number | null; userLongitude: number | null; destinationLatitude?: number | null; destinationLongitude?: number | null; }) => { if (!userLatitude || !userLongitude) { return { latitude: 37.78825, longitude: -122.4324, latitudeDelta: 0.01, longitudeDelta: 0.01, }; } if (!destinationLatitude || !destinationLongitude) { return { latitude: userLatitude, longitude: userLongitude, latitudeDelta: 0.01, longitudeDelta: 0.01, }; } const minLat = Math.min(userLatitude, destinationLatitude); const maxLat = Math.max(userLatitude, destinationLatitude); const minLng = Math.min(userLongitude, destinationLongitude); const maxLng = Math.max(userLongitude, destinationLongitude); const latitudeDelta = (maxLat - minLat) * 1.3; // Adding some padding const longitudeDelta = (maxLng - minLng) * 1.3; // Adding some padding const latitude = (userLatitude + destinationLatitude) / 2; const longitude = (userLongitude + destinationLongitude) / 2; return { latitude, longitude, latitudeDelta, longitudeDelta, }; }; export const calculateDriverTimes = async ({ markers, userLatitude, userLongitude, destinationLatitude, destinationLongitude, }: { markers: MarkerData[]; userLatitude: number | null; userLongitude: number | null; destinationLatitude: number | null; destinationLongitude: number | null; }) => { if ( !userLatitude || !userLongitude || !destinationLatitude || !destinationLongitude ) return; try { const timesPromises = markers.map(async (marker) => { const responseToUser = await fetch( `https://maps.googleapis.com/maps/api/directions/json?origin=${marker.latitude},${marker.longitude}&destination=${userLatitude},${userLongitude}&key=${directionsAPI}`, ); const dataToUser = await responseToUser.json(); const timeToUser = dataToUser.routes[0].legs[0].duration.value; // Time in seconds const responseToDestination = await fetch( `https://maps.googleapis.com/maps/api/directions/json?origin=${userLatitude},${userLongitude}&destination=${destinationLatitude},${destinationLongitude}&key=${directionsAPI}`, ); const dataToDestination = await responseToDestination.json(); const timeToDestination = dataToDestination.routes[0].legs[0].duration.value; // Time in seconds const totalTime = (timeToUser + timeToDestination) / 60; // Total time in minutes const price = (totalTime * 0.5).toFixed(2); // Calculate price based on time return {...marker, time: totalTime, price}; }); return await Promise.all(timesPromises); } catch (error) { console.error("Error calculating driver times:", error); } }; ```
lib/utils.ts ```ts import {Ride} from "@/types/type"; export const sortRides = (rides: Ride[]): Ride[] => { const result = rides.sort((a, b) => { const dateA = new Date(`${a.created_at}T${a.ride_time}`); const dateB = new Date(`${b.created_at}T${b.ride_time}`); return dateB.getTime() - dateA.getTime(); }); return result.reverse(); }; export function formatTime(minutes: number): string { const formattedMinutes = +minutes?.toFixed(0) || 0; if (formattedMinutes < 60) { return `${minutes} min`; } else { const hours = Math.floor(formattedMinutes / 60); const remainingMinutes = formattedMinutes % 60; return `${hours}h ${remainingMinutes}m`; } } export function formatDate(dateString: string): string { const date = new Date(dateString); const day = date.getDate(); const monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; const month = monthNames[date.getMonth()]; const year = date.getFullYear(); return `${day < 10 ? "0" + day : day} ${month} ${year}`; } ```
### Queries
GET Rides SQL Query ```sql SELECT rides.ride_id, rides.origin_address, rides.destination_address, rides.origin_latitude, rides.origin_longitude, rides.destination_latitude, rides.destination_longitude, rides.ride_time, rides.fare_price, rides.payment_status, rides.created_at, 'driver', json_build_object( 'driver_id', drivers.id, 'first_name', drivers.first_name, 'last_name', drivers.last_name, 'profile_image_url', drivers.profile_image_url, 'car_image_url', drivers.car_image_url, 'car_seats', drivers.car_seats, 'rating', drivers.rating ) AS driver FROM rides INNER JOIN drivers ON rides.driver_id = drivers.id WHERE rides.user_email = ${id} ORDER BY rides.created_at DESC; ```
SEED Drivers Query ```sql INSERT INTO drivers (id, first_name, last_name, profile_image_url, car_image_url, car_seats, rating) VALUES ('1', 'James', 'Wilson', 'https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/', 'https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/', 4, '4.80'), ('2', 'David', 'Brown', 'https://ucarecdn.com/6ea6d83d-ef1a-483f-9106-837a3a5b3f67/-/preview/1000x666/', 'https://ucarecdn.com/a3872f80-c094-409c-82f8-c9ff38429327/-/preview/930x932/', 5, '4.60'), ('3', 'Michael', 'Johnson', 'https://ucarecdn.com/0330d85c-232e-4c30-bd04-e5e4d0e3d688/-/preview/826x822/', 'https://ucarecdn.com/289764fb-55b6-4427-b1d1-f655987b4a14/-/preview/930x932/', 4, '4.70'), ('4', 'Robert', 'Green', 'https://ucarecdn.com/fdfc54df-9d24-40f7-b7d3-6f391561c0db/-/preview/626x417/', 'https://ucarecdn.com/b6fb3b55-7676-4ff3-8484-fb115e268d32/-/preview/930x932/', 4, '4.90'); `````
### Schema
CREATE Drivers Table SQL Query ```sql CREATE TABLE drivers ( id SERIAL PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, profile_image_url TEXT, car_image_url TEXT, car_seats INTEGER NOT NULL CHECK (car_seats > 0), rating DECIMAL(3, 2) CHECK (rating >= 0 AND rating <= 5) ); ```
CREATE Rides Table SQL Query ```sql CREATE TABLE rides ( ride_id SERIAL PRIMARY KEY, origin_address VARCHAR(255) NOT NULL, destination_address VARCHAR(255) NOT NULL, origin_latitude DECIMAL(9, 6) NOT NULL, origin_longitude DECIMAL(9, 6) NOT NULL, destination_latitude DECIMAL(9, 6) NOT NULL, destination_longitude DECIMAL(9, 6) NOT NULL, ride_time INTEGER NOT NULL, fare_price DECIMAL(10, 2) NOT NULL CHECK (fare_price >= 0), payment_status VARCHAR(20) NOT NULL, driver_id INTEGER REFERENCES drivers(id), user_id VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ```
CREATE Users Table SQL ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, clerk_id VARCHAR(50) UNIQUE NOT NULL ); ```
### Mock Data
Mock Drivers ```js [ { "id": "1", "first_name": "James", "last_name": "Wilson", "profile_image_url": "https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/", "car_image_url": "https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/", "car_seats": 4, "rating": "4.80" }, { "id": "2", "first_name": "David", "last_name": "Brown", "profile_image_url": "https://ucarecdn.com/6ea6d83d-ef1a-483f-9106-837a3a5b3f67/-/preview/1000x666/", "car_image_url": "https://ucarecdn.com/a3872f80-c094-409c-82f8-c9ff38429327/-/preview/930x932/", "car_seats": 5, "rating": "4.60" }, { "id": "3", "first_name": "Michael", "last_name": "Johnson", "profile_image_url": "https://ucarecdn.com/0330d85c-232e-4c30-bd04-e5e4d0e3d688/-/preview/826x822/", "car_image_url": "https://ucarecdn.com/289764fb-55b6-4427-b1d1-f655987b4a14/-/preview/930x932/", "car_seats": 4, "rating": "4.70" }, { "id": "4", "first_name": "Robert", "last_name": "Green", "profile_image_url": "https://ucarecdn.com/fdfc54df-9d24-40f7-b7d3-6f391561c0db/-/preview/626x417/", "car_image_url": "https://ucarecdn.com/b6fb3b55-7676-4ff3-8484-fb115e268d32/-/preview/930x932/", "car_seats": 4, "rating": "4.90" } ] ```
Mock Rides ```js [ { "ride_id": "1", "origin_address": "Kathmandu, Nepal", "destination_address": "Pokhara, Nepal", "origin_latitude": "27.717245", "origin_longitude": "85.323961", "destination_latitude": "28.209583", "destination_longitude": "83.985567", "ride_time": 391, "fare_price": "19500.00", "payment_status": "paid", "driver_id": 2, "user_id": "1", "created_at": "2024-08-12 05:19:20.620007", "driver": { "driver_id": "2", "first_name": "David", "last_name": "Brown", "profile_image_url": "https://ucarecdn.com/6ea6d83d-ef1a-483f-9106-837a3a5b3f67/-/preview/1000x666/", "car_image_url": "https://ucarecdn.com/a3872f80-c094-409c-82f8-c9ff38429327/-/preview/930x932/", "car_seats": 5, "rating": "4.60" } }, { "ride_id": "2", "origin_address": "Jalkot, MH", "destination_address": "Pune, Maharashtra, India", "origin_latitude": "18.609116", "origin_longitude": "77.165873", "destination_latitude": "18.520430", "destination_longitude": "73.856744", "ride_time": 491, "fare_price": "24500.00", "payment_status": "paid", "driver_id": 1, "user_id": "1", "created_at": "2024-08-12 06:12:17.683046", "driver": { "driver_id": "1", "first_name": "James", "last_name": "Wilson", "profile_image_url": "https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/", "car_image_url": "https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/", "car_seats": 4, "rating": "4.80" } }, { "ride_id": "3", "origin_address": "Zagreb, Croatia", "destination_address": "Rijeka, Croatia", "origin_latitude": "45.815011", "origin_longitude": "15.981919", "destination_latitude": "45.327063", "destination_longitude": "14.442176", "ride_time": 124, "fare_price": "6200.00", "payment_status": "paid", "driver_id": 1, "user_id": "1", "created_at": "2024-08-12 08:49:01.809053", "driver": { "driver_id": "1", "first_name": "James", "last_name": "Wilson", "profile_image_url": "https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/", "car_image_url": "https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/", "car_seats": 4, "rating": "4.80" } }, { "ride_id": "4", "origin_address": "Okayama, Japan", "destination_address": "Osaka, Japan", "origin_latitude": "34.655531", "origin_longitude": "133.919795", "destination_latitude": "34.693725", "destination_longitude": "135.502254", "ride_time": 159, "fare_price": "7900.00", "payment_status": "paid", "driver_id": 3, "user_id": "1", "created_at": "2024-08-12 18:43:54.297838", "driver": { "driver_id": "3", "first_name": "Michael", "last_name": "Johnson", "profile_image_url": "https://ucarecdn.com/0330d85c-232e-4c30-bd04-e5e4d0e3d688/-/preview/826x822/", "car_image_url": "https://ucarecdn.com/289764fb-55b6-4427-b1d1-f655987b4a14/-/preview/930x932/", "car_seats": 4, "rating": "4.70" } } ] ```
### API Endpoints
(api)/ride/create+api.ts ```ts import {neon} from "@neondatabase/serverless"; export async function POST(request: Request) { try { const body = await request.json(); const { origin_address, destination_address, origin_latitude, origin_longitude, destination_latitude, destination_longitude, ride_time, fare_price, payment_status, driver_id, user_id, } = body; if ( !origin_address || !destination_address || !origin_latitude || !origin_longitude || !destination_latitude || !destination_longitude || !ride_time || !fare_price || !payment_status || !driver_id || !user_id ) { return Response.json( {error: "Missing required fields"}, {status: 400}, ); } const sql = neon(`${process.env.DATABASE_URL}`); const response = await sql` INSERT INTO rides ( origin_address, destination_address, origin_latitude, origin_longitude, destination_latitude, destination_longitude, ride_time, fare_price, payment_status, driver_id, user_id ) VALUES ( ${origin_address}, ${destination_address}, ${origin_latitude}, ${origin_longitude}, ${destination_latitude}, ${destination_longitude}, ${ride_time}, ${fare_price}, ${payment_status}, ${driver_id}, ${user_id} ) RETURNING *; `; return Response.json({data: response[0]}, {status: 201}); } catch (error) { console.error("Error inserting data into recent_rides:", error); return Response.json({error: "Internal Server Error"}, {status: 500}); } } ```
(api)/ride/[id]+api.ts ```ts import {neon} from "@neondatabase/serverless"; export async function GET(request: Request, {id}: { id: string }) { if (!id) return Response.json({error: "Missing required fields"}, {status: 400}); try { const sql = neon(`${process.env.DATABASE_URL}`); const response = await sql` SELECT rides.ride_id, rides.origin_address, rides.destination_address, rides.origin_latitude, rides.origin_longitude, rides.destination_latitude, rides.destination_longitude, rides.ride_time, rides.fare_price, rides.payment_status, rides.created_at, 'driver', json_build_object( 'driver_id', drivers.id, 'first_name', drivers.first_name, 'last_name', drivers.last_name, 'profile_image_url', drivers.profile_image_url, 'car_image_url', drivers.car_image_url, 'car_seats', drivers.car_seats, 'rating', drivers.rating ) AS driver FROM rides INNER JOIN drivers ON rides.driver_id = drivers.id WHERE rides.user_id = ${id} ORDER BY rides.created_at DESC; `; return Response.json({data: response}); } catch (error) { console.error("Error fetching recent rides:", error); return Response.json({error: "Internal Server Error"}, {status: 500}); } } ```
### Screens
(root)/book-ride ```tsx import {useUser} from "@clerk/clerk-expo"; import {Image, Text, View} from "react-native"; import RideLayout from "@/components/RideLayout"; import {icons} from "@/constants"; import {formatTime} from "@/lib/utils"; import {useDriverStore, useLocationStore} from "@/store"; const BookRide = () => { const {user} = useUser(); const {userAddress, destinationAddress} = useLocationStore(); const {drivers, selectedDriver} = useDriverStore(); const driverDetails = drivers?.filter( (driver) => +driver.id === selectedDriver, )[0]; return ( <> Ride Information {driverDetails?.title} {driverDetails?.rating} Ride Price ${driverDetails?.price} Pickup Time {formatTime(driverDetails?.time!)} Car Seats {driverDetails?.car_seats} {userAddress} {destinationAddress} ); }; export default BookRide; ```
(root)/(tabs)/profile ```tsx import {useUser} from "@clerk/clerk-expo"; import {Image, ScrollView, Text, View} from "react-native"; import {SafeAreaView} from "react-native-safe-area-context"; import InputField from "@/components/input-field"; const Profile = () => { const {user} = useUser(); return ( My profile ); }; export default Profile; ```
(root)/(tabs)/chat ```tsx import {Image, ScrollView, Text, View} from "react-native"; import {SafeAreaView} from "react-native-safe-area-context"; import {images} from "@/constants"; const Chat = () => { return ( Chat message No Messages Yet Start a conversation with your friends and family ); }; export default Chat; ```
### Other
store/index.ts ```ts import {create} from "zustand"; import {DriverStore, LocationStore, MarkerData} from "@/types/type"; export const useLocationStore = create((set) => ({ userLatitude: null, userLongitude: null, userAddress: null, destinationLatitude: null, destinationLongitude: null, destinationAddress: null, setUserLocation: ({ latitude, longitude, address, }: { latitude: number; longitude: number; address: string; }) => { set(() => ({ userLatitude: latitude, userLongitude: longitude, userAddress: address, })); // If driver is selected and now a new location is set, clear the selected driver const {selectedDriver, clearSelectedDriver} = useDriverStore.getState(); if (selectedDriver) clearSelectedDriver(); }, setDestinationLocation: ({ latitude, longitude, address, }: { latitude: number; longitude: number; address: string; }) => { set(() => ({ destinationLatitude: latitude, destinationLongitude: longitude, destinationAddress: address, })); // If driver is selected and now a new location is set, clear the selected driver const {selectedDriver, clearSelectedDriver} = useDriverStore.getState(); if (selectedDriver) clearSelectedDriver(); }, })); export const useDriverStore = create((set) => ({ drivers: [] as MarkerData[], selectedDriver: null, setSelectedDriver: (driverId: number) => set(() => ({selectedDriver: driverId})), setDrivers: (drivers: MarkerData[]) => set(() => ({drivers})), clearSelectedDriver: () => set(() => ({selectedDriver: null})), })); ```
## πŸ”— Links You can find important links mentioned in the YouTube video below: - Expo NativeWind Setup - TypeScript Support for NativeWind - Eslint and Prettier Setup - Download FREE WebStorm - Serverless NeonDB - Clerk Auth - Database Mastery Course - Clerk Expo Quickstart - Clerk Expo OAuth - Geoapify Map - Stripe React Native SDK - Stripe ## πŸ“¦ Assets Assets used in the project can be found [here](https://drive.google.com/file/d/1ekttG-aCyy4g0SKqLSrEn_uHf2MJMRJ4/view?usp=sharing) ## πŸš€ More **Advance your skills with Next.js 14 Pro Course** Enjoyed creating this project? Dive deeper into our PRO courses for a richer learning adventure. They're packed with detailed explanations, cool features, and exercises to boost your skills. Give it a go! Project Banner

**Accelerate your professional journey with the Expert Training program** And if you're hungry for more than just a course and want to understand how we learn and tackle tech challenges, hop into our personalized masterclass. We cover best practices, different web skills, and offer mentorship to boost your confidence. Let's learn and grow together! Project Banner #