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!
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.
- React Native
- Expo
- Stripe
- PostgreSQL
- Google Maps
- zustand
- Clerk
- Tailwind CSS
👉 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
Follow these steps to set up the project locally on your machine.
Prerequisites
Make sure you have the following installed on your machine:
Cloning the Repository
git clone https://github.com/JavaScript-Mastery-Pro/uber.git
cd uberInstallation
Install the project dependencies using npm:
npm installSet Up Environment Variables
Create a new file named .env in the root of your project and add the following content:
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, Stripe, NeonDB, Google Maps and geoapify websites respectively.
Running the Project
npx expo startDownload the Expo Go app and Scan the QR code on your respective device to view the project.
Here are some code snippets from the project to help you get started quickly.
.vscode/settings.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
/** @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
import{TextInputProps,TouchableOpacityProps}from"react-native";declareinterfaceDriver{driver_id: number;first_name: string;last_name: string;profile_image_url: string;car_image_url: string;car_seats: number;rating: number;}declareinterfaceMarkerData{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;}declareinterfaceMapProps{destinationLatitude?: number;destinationLongitude?: number;onDriverTimesCalculated?: (driversWithTimes: MarkerData[])=>void;selectedDriver?: number|null;onMapReady?: ()=>void;}declareinterfaceRide{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;};}declareinterfaceButtonPropsextendsTouchableOpacityProps{title: string;bgVariant?: "primary"|"secondary"|"danger"|"outline"|"success";textVariant?: "primary"|"default"|"secondary"|"danger"|"success";IconLeft?: React.ComponentType<any>;IconRight?: React.ComponentType<any>;className?: string;}declareinterfaceGoogleInputProps{icon?: string;initialLocation?: string;containerStyle?: string;textInputBackgroundColor?: string;handlePress: ({
latitude,
longitude,
address,}: {latitude: number;longitude: number;address: string;})=>void;}declareinterfaceInputFieldPropsextendsTextInputProps{label: string;icon?: any;secureTextEntry?: boolean;labelStyle?: string;containerStyle?: string;inputStyle?: string;iconStyle?: string;className?: string;}declareinterfacePaymentProps{fullName: string;email: string;amount: string;driverId: number;rideTime: number;}declareinterfaceLocationStore{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;}declareinterfaceDriverStore{drivers: MarkerData[];selectedDriver: number|null;setSelectedDriver: (driverId: number)=>void;setDrivers: (drivers: MarkerData[])=>void;clearSelectedDriver: ()=>void;}declareinterfaceDriverCardProps{item: MarkerData;selected: number;setSelected: ()=>void;}types/image.d.ts
declare module "*.png"{constvalue: any;exportdefaultvalue;}declare module "*.jpg"{constvalue: any;exportdefaultvalue;}declare module "*.jpeg"{constvalue: any;exportdefaultvalue;}declare module "*.gif"{constvalue: any;exportdefaultvalue;}declare module "*.svg"{constvalue: any;exportdefaultvalue;}constants/index.ts
importarrowDownfrom"@/assets/icons/arrow-down.png";importarrowUpfrom"@/assets/icons/arrow-up.png";importbackArrowfrom"@/assets/icons/back-arrow.png";importchatfrom"@/assets/icons/chat.png";importcheckmarkfrom"@/assets/icons/check.png";importclosefrom"@/assets/icons/close.png";importdollarfrom"@/assets/icons/dollar.png";importemailfrom"@/assets/icons/email.png";importeyecrossfrom"@/assets/icons/eyecross.png";importgooglefrom"@/assets/icons/google.png";importhomefrom"@/assets/icons/home.png";importlistfrom"@/assets/icons/list.png";importlockfrom"@/assets/icons/lock.png";importmapfrom"@/assets/icons/map.png";importmarkerfrom"@/assets/icons/marker.png";importoutfrom"@/assets/icons/out.png";importpersonfrom"@/assets/icons/person.png";importpinfrom"@/assets/icons/pin.png";importpointfrom"@/assets/icons/point.png";importprofilefrom"@/assets/icons/profile.png";importsearchfrom"@/assets/icons/search.png";importselectedMarkerfrom"@/assets/icons/selected-marker.png";importstarfrom"@/assets/icons/star.png";importtargetfrom"@/assets/icons/target.png";importtofrom"@/assets/icons/to.png";importcheckfrom"@/assets/images/check.png";importgetStartedfrom"@/assets/images/get-started.png";importmessagefrom"@/assets/images/message.png";importnoResultfrom"@/assets/images/no-result.png";importonboarding1from"@/assets/images/onboarding1.png";importonboarding2from"@/assets/images/onboarding2.png";importonboarding3from"@/assets/images/onboarding3.png";importsignUpCarfrom"@/assets/images/signup-car.png";exportconstimages={
onboarding1,
onboarding2,
onboarding3,
getStarted,
signUpCar,
check,
noResult,
message,};exportconsticons={
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,};exportconstonboarding=[{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,},];exportconstdata={
onboarding,};Root Layout Fonts
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/CustomButton
import{TouchableOpacity,Text}from"react-native";import{ButtonProps}from"@/types/type";constgetBgVariantStyle=(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]";}};constgetTextVariantStyle=(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";}};constCustomButton=({
onPress,
title,
bgVariant ="primary",
textVariant ="default",
IconLeft,
IconRight,
className,
...props}: ButtonProps)=>{return(<TouchableOpacityonPress={onPress}className={`w-full rounded-full p-3 flex flex-row justify-center items-center shadow-md shadow-neutral-400/70 ${getBgVariantStyle(bgVariant)}${className}`}{...props}>{IconLeft&&<IconLeft/>}<TextclassName={`text-lg font-bold ${getTextVariantStyle(textVariant)}`}>{title}</Text>{IconRight&&<IconRight/>}</TouchableOpacity>);};exportdefaultCustomButton;components/InputField
import{TextInput,View,Text,Image,KeyboardAvoidingView,TouchableWithoutFeedback,Keyboard,Platform,}from"react-native";import{InputFieldProps}from"@/types/type";constInputField=({
label,
icon,
secureTextEntry =false,
labelStyle,
containerStyle,
inputStyle,
iconStyle,
className,
...props}: InputFieldProps)=>{return(<KeyboardAvoidingViewbehavior={Platform.OS==="ios" ? "padding" : "height"}><TouchableWithoutFeedbackonPress={Keyboard.dismiss}><ViewclassName="my-2 w-full"><TextclassName={`text-lg font-JakartaSemiBold mb-3 ${labelStyle}`}>{label}</Text><ViewclassName={`flex flex-row justify-start items-center relative bg-neutral-100 rounded-full border border-neutral-100 focus:border-primary-500 ${containerStyle}`}>{icon&&(<Imagesource={icon}className={`w-6 h-6 ml-4 ${iconStyle}`}/>)}<TextInputclassName={`rounded-full p-4 font-JakartaSemiBold text-[15px] flex-1 ${inputStyle} text-left`}secureTextEntry={secureTextEntry}{...props}/></View></View></TouchableWithoutFeedback></KeyboardAvoidingView>);};exportdefaultInputField;components/DriverCard.tsx
importReactfrom"react";import{Image,Text,TouchableOpacity,View}from"react-native";import{icons}from"@/constants";import{formatTime}from"@/lib/utils";import{DriverCardProps}from"@/types/type";constDriverCard=({item, selected, setSelected}: DriverCardProps)=>{return(<TouchableOpacityonPress={setSelected}className={`${selected===item.id ? "bg-general-600" : "bg-white"} flex flex-row items-center justify-between py-5 px-3 rounded-xl`}><Imagesource={{uri: item.profile_image_url}}className="w-14 h-14 rounded-full"/><ViewclassName="flex-1 flex flex-col items-start justify-center mx-3"><ViewclassName="flex flex-row items-center justify-start mb-1"><TextclassName="text-lg font-JakartaRegular">{item.title}</Text><ViewclassName="flex flex-row items-center space-x-1 ml-2"><Imagesource={icons.star}className="w-3.5 h-3.5"/><TextclassName="text-sm font-JakartaRegular">4</Text></View></View><ViewclassName="flex flex-row items-center justify-start"><ViewclassName="flex flex-row items-center"><Imagesource={icons.dollar}className="w-4 h-4"/><TextclassName="text-sm font-JakartaRegular ml-1">
${item.price}</Text></View><TextclassName="text-sm font-JakartaRegular text-general-800 mx-1">
|
</Text><TextclassName="text-sm font-JakartaRegular text-general-800">{formatTime(item.time!)}</Text><TextclassName="text-sm font-JakartaRegular text-general-800 mx-1">
|
</Text><TextclassName="text-sm font-JakartaRegular text-general-800">{item.car_seats} seats
</Text></View></View><Imagesource={{uri: item.car_image_url}}className="h-14 w-14"resizeMode="contain"/></TouchableOpacity>);};exportdefaultDriverCard;lib/fetch.ts
import{useState,useEffect,useCallback}from"react";exportconstfetchAPI=async(url: string,options?: RequestInit)=>{try{constresponse=awaitfetch(url,options);if(!response.ok){newError(`HTTP error! status: ${response.status}`);}returnawaitresponse.json();}catch(error){console.error("Fetch error:",error);throwerror;}};exportconstuseFetch=<T>(url: string,options?: RequestInit)=>{const[data,setData]=useState<T|null>(null);const[loading,setLoading]=useState(false);const[error,setError]=useState<string|null>(null);constfetchData=useCallback(async()=>{setLoading(true);setError(null);try{constresult=awaitfetchAPI(url,options);setData(result.data);}catch(err){setError((errasError).message);}finally{setLoading(false);}},[url,options]);useEffect(()=>{fetchData();},[fetchData]);return{data, loading, error,refetch: fetchData};};lib/map.ts
import{Driver,MarkerData}from"@/types/type";constdirectionsAPI=process.env.EXPO_PUBLIC_GOOGLE_API_KEY;exportconstgenerateMarkersFromData=({
data,
userLatitude,
userLongitude,}: {data: Driver[];userLatitude: number;userLongitude: number;}): MarkerData[]=>{returndata.map((driver)=>{constlatOffset=(Math.random()-0.5)*0.01;// Random offset between -0.005 and 0.005constlngOffset=(Math.random()-0.5)*0.01;// Random offset between -0.005 and 0.005return{latitude: userLatitude+latOffset,longitude: userLongitude+lngOffset,title: `${driver.first_name}${driver.last_name}`,
...driver,};});};exportconstcalculateRegion=({
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,};}constminLat=Math.min(userLatitude,destinationLatitude);constmaxLat=Math.max(userLatitude,destinationLatitude);constminLng=Math.min(userLongitude,destinationLongitude);constmaxLng=Math.max(userLongitude,destinationLongitude);constlatitudeDelta=(maxLat-minLat)*1.3;// Adding some paddingconstlongitudeDelta=(maxLng-minLng)*1.3;// Adding some paddingconstlatitude=(userLatitude+destinationLatitude)/2;constlongitude=(userLongitude+destinationLongitude)/2;return{
latitude,
longitude,
latitudeDelta,
longitudeDelta,};};exportconstcalculateDriverTimes=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{consttimesPromises=markers.map(async(marker)=>{constresponseToUser=awaitfetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${marker.latitude},${marker.longitude}&destination=${userLatitude},${userLongitude}&key=${directionsAPI}`,);constdataToUser=awaitresponseToUser.json();consttimeToUser=dataToUser.routes[0].legs[0].duration.value;// Time in secondsconstresponseToDestination=awaitfetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${userLatitude},${userLongitude}&destination=${destinationLatitude},${destinationLongitude}&key=${directionsAPI}`,);constdataToDestination=awaitresponseToDestination.json();consttimeToDestination=dataToDestination.routes[0].legs[0].duration.value;// Time in secondsconsttotalTime=(timeToUser+timeToDestination)/60;// Total time in minutesconstprice=(totalTime*0.5).toFixed(2);// Calculate price based on timereturn{...marker,time: totalTime, price};});returnawaitPromise.all(timesPromises);}catch(error){console.error("Error calculating driver times:",error);}};lib/utils.ts
import{Ride}from"@/types/type";exportconstsortRides=(rides: Ride[]): Ride[]=>{constresult=rides.sort((a,b)=>{constdateA=newDate(`${a.created_at}T${a.ride_time}`);constdateB=newDate(`${b.created_at}T${b.ride_time}`);returndateB.getTime()-dateA.getTime();});returnresult.reverse();};exportfunctionformatTime(minutes: number): string{constformattedMinutes=+minutes?.toFixed(0)||0;if(formattedMinutes<60){return`${minutes} min`;}else{consthours=Math.floor(formattedMinutes/60);constremainingMinutes=formattedMinutes%60;return`${hours}h ${remainingMinutes}m`;}}exportfunctionformatDate(dateString: string): string{constdate=newDate(dateString);constday=date.getDate();constmonthNames=["January","February","March","April","May","June","July","August","September","October","November","December",];constmonth=monthNames[date.getMonth()];constyear=date.getFullYear();return`${day<10 ? "0"+day : day}${month}${year}`;}GET Rides SQL Query
SELECTrides.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 ONrides.driver_id=drivers.idWHERErides.user_email= ${id}
ORDER BYrides.created_atDESC;SEED Drivers Query
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');CREATE Drivers Table SQL Query
CREATETABLEdrivers (
id SERIALPRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
profile_image_url TEXT,
car_image_url TEXT,
car_seats INTEGERNOT NULLCHECK (car_seats >0),
rating DECIMAL(3, 2) CHECK (rating >=0AND rating <=5)
);CREATE Rides Table SQL Query
CREATETABLErides (
ride_id SERIALPRIMARY 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 INTEGERNOT NULL,
fare_price DECIMAL(10, 2) NOT NULLCHECK (fare_price >=0),
payment_status VARCHAR(20) NOT NULL,
driver_id INTEGERREFERENCES drivers(id),
user_id VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); CREATE Users Table SQL
CREATETABLEusers (
id SERIALPRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
clerk_id VARCHAR(50) UNIQUE NOT NULL
);Mock Drivers
[{"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
[{"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)/ride/create+api.ts
import{neon}from"@neondatabase/serverless";exportasyncfunctionPOST(request: Request){try{constbody=awaitrequest.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){returnResponse.json({error: "Missing required fields"},{status: 400},);}constsql=neon(`${process.env.DATABASE_URL}`);constresponse=awaitsql` 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 *; `;returnResponse.json({data: response[0]},{status: 201});}catch(error){console.error("Error inserting data into recent_rides:",error);returnResponse.json({error: "Internal Server Error"},{status: 500});}}(api)/ride/[id]+api.ts
import{neon}from"@neondatabase/serverless";exportasyncfunctionGET(request: Request,{id}: {id: string}){if(!id)returnResponse.json({error: "Missing required fields"},{status: 400});try{constsql=neon(`${process.env.DATABASE_URL}`);constresponse=awaitsql` 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; `;returnResponse.json({data: response});}catch(error){console.error("Error fetching recent rides:",error);returnResponse.json({error: "Internal Server Error"},{status: 500});}}(root)/book-ride
import{useUser}from"@clerk/clerk-expo";import{Image,Text,View}from"react-native";importRideLayoutfrom"@/components/RideLayout";import{icons}from"@/constants";import{formatTime}from"@/lib/utils";import{useDriverStore,useLocationStore}from"@/store";constBookRide=()=>{const{user}=useUser();const{userAddress, destinationAddress}=useLocationStore();const{drivers, selectedDriver}=useDriverStore();constdriverDetails=drivers?.filter((driver)=>+driver.id===selectedDriver,)[0];return(<RideLayouttitle="Book Ride"><><TextclassName="text-xl font-JakartaSemiBold mb-3">
Ride Information
</Text><ViewclassName="flex flex-col w-full items-center justify-center mt-10"><Imagesource={{uri: driverDetails?.profile_image_url}}className="w-28 h-28 rounded-full"/><ViewclassName="flex flex-row items-center justify-center mt-5 space-x-2"><TextclassName="text-lg font-JakartaSemiBold">{driverDetails?.title}</Text><ViewclassName="flex flex-row items-center space-x-0.5"><Imagesource={icons.star}className="w-5 h-5"resizeMode="contain"/><TextclassName="text-lg font-JakartaRegular">{driverDetails?.rating}</Text></View></View></View><ViewclassName="flex flex-col w-full items-start justify-center py-3 px-5 rounded-3xl bg-general-600 mt-5"><ViewclassName="flex flex-row items-center justify-between w-full border-b border-white py-3"><TextclassName="text-lg font-JakartaRegular">Ride Price</Text><TextclassName="text-lg font-JakartaRegular text-[#0CC25F]">
${driverDetails?.price}</Text></View><ViewclassName="flex flex-row items-center justify-between w-full border-b border-white py-3"><TextclassName="text-lg font-JakartaRegular">Pickup Time</Text><TextclassName="text-lg font-JakartaRegular">{formatTime(driverDetails?.time!)}</Text></View><ViewclassName="flex flex-row items-center justify-between w-full py-3"><TextclassName="text-lg font-JakartaRegular">Car Seats</Text><TextclassName="text-lg font-JakartaRegular">{driverDetails?.car_seats}</Text></View></View><ViewclassName="flex flex-col w-full items-start justify-center mt-5"><ViewclassName="flex flex-row items-center justify-start mt-3 border-t border-b border-general-700 w-full py-3"><Imagesource={icons.to}className="w-6 h-6"/><TextclassName="text-lg font-JakartaRegular ml-2">{userAddress}</Text></View><ViewclassName="flex flex-row items-center justify-start border-b border-general-700 w-full py-3"><Imagesource={icons.point}className="w-6 h-6"/><TextclassName="text-lg font-JakartaRegular ml-2">{destinationAddress}</Text></View></View></></RideLayout>);};exportdefaultBookRide;(root)/(tabs)/profile
import{useUser}from"@clerk/clerk-expo";import{Image,ScrollView,Text,View}from"react-native";import{SafeAreaView}from"react-native-safe-area-context";importInputFieldfrom"@/components/input-field";constProfile=()=>{const{user}=useUser();return(<SafeAreaViewclassName="flex-1"><ScrollViewclassName="px-5"contentContainerStyle={{paddingBottom: 120}}><TextclassName="text-2xl font-JakartaBold my-5">My profile</Text><ViewclassName="flex items-center justify-center my-5"><Imagesource={{uri: user?.externalAccounts[0]?.imageUrl??user?.imageUrl,}}style={{width: 110,height: 110,borderRadius: 110/2}}className=" rounded-full h-[110px] w-[110px] border-[3px] border-white shadow-sm shadow-neutral-300"/></View><ViewclassName="flex flex-col items-start justify-center bg-white rounded-lg shadow-sm shadow-neutral-300 px-5 py-3"><ViewclassName="flex flex-col items-start justify-start w-full"><InputFieldlabel="First name"placeholder={user?.firstName||"Not Found"}containerStyle="w-full"inputStyle="p-3.5"editable={false}/><InputFieldlabel="Last name"placeholder={user?.lastName||"Not Found"}containerStyle="w-full"inputStyle="p-3.5"editable={false}/><InputFieldlabel="Email"placeholder={user?.primaryEmailAddress?.emailAddress||"Not Found"}containerStyle="w-full"inputStyle="p-3.5"editable={false}/><InputFieldlabel="Phone"placeholder={user?.primaryPhoneNumber?.phoneNumber||"Not Found"}containerStyle="w-full"inputStyle="p-3.5"editable={false}/></View></View></ScrollView></SafeAreaView>);};exportdefaultProfile;(root)/(tabs)/chat
import{Image,ScrollView,Text,View}from"react-native";import{SafeAreaView}from"react-native-safe-area-context";import{images}from"@/constants";constChat=()=>{return(<SafeAreaViewclassName="flex-1 bg-white p-5"><ScrollViewcontentContainerStyle={{flexGrow: 1}}><TextclassName="text-2xl font-JakartaBold">Chat</Text><ViewclassName="flex-1 h-fit flex justify-center items-center"><Imagesource={images.message}alt="message"className="w-full h-40"resizeMode="contain"/><TextclassName="text-3xl font-JakartaBold mt-3">
No Messages Yet
</Text><TextclassName="text-base mt-2 text-center px-7">
Start a conversation with your friends and family
</Text></View></ScrollView></SafeAreaView>);};exportdefaultChat;store/index.ts
import{create}from"zustand";import{DriverStore,LocationStore,MarkerData}from"@/types/type";exportconstuseLocationStore=create<LocationStore>((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 driverconst{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 driverconst{selectedDriver, clearSelectedDriver}=useDriverStore.getState();if(selectedDriver)clearSelectedDriver();},}));exportconstuseDriverStore=create<DriverStore>((set)=>({drivers: []asMarkerData[],selectedDriver: null,setSelectedDriver: (driverId: number)=>set(()=>({selectedDriver: driverId})),setDrivers: (drivers: MarkerData[])=>set(()=>({drivers})),clearSelectedDriver: ()=>set(()=>({selectedDriver: null})),}));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 used in the project can be found here
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!

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!
