Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/r3ss1/real-estate-app

A real estate app with React Native, Expo, Appwrite & TypeScript. Features Google auth, dynamic listings, user profiles and Tailwind CSS.
https://github.com/r3ss1/real-estate-app

appwrite expo nativewind react-native tailwindcss typescript

Last synced: 18 days ago
JSON representation

A real estate app with React Native, Expo, Appwrite & TypeScript. Features Google auth, dynamic listings, user profiles and Tailwind CSS.

Awesome Lists containing this project

README

        





Project Banner




expo
typescript
appwrite
tailwindcss

A Real Estate App


In this project, I am coding along with Adrian Hajdin from JavaScript Mastery (YouTube) to build a real estate application.







Project Video


Adrian Hajdin GitHub







Initiated
Completed
Progress - 29Dec24
YT video lenght




December 2024
Est. ~ mid 2025


2%

3 hours



## πŸ€– Introduction

Full-stack Real Estate application with React Native, featuring Google authentication, dynamic property listings, and user profiles. Designed with modern tools like Expo SDK 52, Appwrite, Tailwind CSS, and TypeScript for a seamless and scalable experience.

If you're getting started and need assistance or face any bugs, join our active Discord community with over **50k+** members. It's a place where people help each other out.

## βš™οΈ Tech Stack

- Expo
- React Native
- TypeScript
- Nativewind
- Appwrite
- Tailwind CSS

## πŸ”‹ Features

πŸ‘‰ **Authentication with Google**: Secure and seamless user sign-ins using Google’s authentication service.

πŸ‘‰ **Home Page**: Displays the latest and recommended properties with powerful search and filter functionality.

πŸ‘‰ **Explore Page**: Allows users to browse all types of properties with a clean and intuitive interface.

πŸ‘‰ **Property Details Page**: Provides comprehensive information about individual properties, including images and key details.

πŸ‘‰ **Profile Page**: Customizable user settings and profile management

πŸ‘‰ **Centralized Data Fetching**: Custom-built solution inspired by TanStack’s useQuery for efficient API calls.

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/r3ss1/Real-Estate-App.git
cd Real-Estate-App
```

**Installation**

```bash
npm install
```

**Set Up Environment Variables**

Create a new file named `.env.local` in the root of your project and add the following content:

```env
EXPO_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
EXPO_PUBLIC_APPWRITE_PROJECT_ID=
EXPO_PUBLIC_APPWRITE_DATABASE_ID=
EXPO_PUBLIC_APPWRITE_GALLERIES_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_REVIEWS_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_AGENTS_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_PROPERTIES_COLLECTION_ID=
```

Replace the values with your actual Appwrite credentials. You can obtain these credentials by signing up & creating a new project on the [Appwrite website](https://apwr.dev/JSM050).

**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).

## πŸ•ΈοΈ Snippets

lib/data.ts

```ts
export const galleryImages = [
"https://images.unsplash.com/photo-1507089947368-19c1da9775ae?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://unsplash.com/photos/comfort-room-with-white-bathtub-and-brown-wooden-cabinets-CMejBwGAdGk",
"https://images.unsplash.com/photo-1638799869566-b17fa794c4de?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1560185009-dddeb820c7b7?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1641910532059-ad684fd3049c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1621293954908-907159247fc8?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1604328702728-d26d2062c20b?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1600435335786-d74d2bb6de37?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1560448204-603b3fc33ddc?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1635108198979-9806fdf275c6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];

export const agentImages = [
"https://images.unsplash.com/photo-1691335053879-02096d6ee2ca?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1544723495-432537d12f6c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1542507464418-09c375b86bbe?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1534308143481-c55f00be8bd7?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];

export const reviewImages = [
"https://images.unsplash.com/photo-1517331671191-ddc2c6d3ebd1?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1474176857210-7287d38d27c6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1511551203524-9a24350a5771?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1507591064344-4c6ce005b128?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];

export const propertiesImages = [
"https://images.unsplash.com/photo-1580587771525-78b9dba3b914?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1605146768851-eda79da39897?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1568605114967-8130f3a36994?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1564013799919-ab600027ffc6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1561753757-d8880c5a3551?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1551241090-67de81d3541c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1697299262049-e9b5fa1e9761?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1719299225324-301bad5c333c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1582063289852-62e3ba2747f8?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1516095901529-0ef7be431a4f?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1600585153490-76fb20a32601?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1605276373954-0c4a0dac5b12?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1583608205776-bfd35f0d9f83?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1720432972486-2d53db5badf0?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
```

lib/seed.ts

```ts
import { ID } from "react-native-appwrite";
import { databases, config } from "./appwrite";
import {
agentImages,
galleryImages,
propertiesImages,
reviewImages,
} from "./data";

const COLLECTIONS = {
AGENT: config.agentsCollectionId,
REVIEWS: config.reviewsCollectionId,
GALLERY: config.galleriesCollectionId,
PROPERTY: config.propertiesCollectionId,
};

const propertyTypes = [
"House",
"Townhomes",
"Condos",
"Duplexes",
"Studios",
"Villa",
"Apartments",
"Others",
];

const facilities = [
"Laundry",
"Car Parking",
"Sports Center",
"Cutlery",
"Gym",
"Swimming pool",
"Wifi",
"Pet Center",
];

function getRandomSubset(
array: T[],
minItems: number,
maxItems: number
): T[] {
if (minItems > maxItems) {
throw new Error("minItems cannot be greater than maxItems");
}
if (minItems < 0 || maxItems > array.length) {
throw new Error(
"minItems or maxItems are out of valid range for the array"
);
}

// Generate a random size for the subset within the range [minItems, maxItems]
const subsetSize =
Math.floor(Math.random() * (maxItems - minItems + 1)) + minItems;

// Create a copy of the array to avoid modifying the original
const arrayCopy = [...array];

// Shuffle the array copy using Fisher-Yates algorithm
for (let i = arrayCopy.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[arrayCopy[i], arrayCopy[randomIndex]] = [
arrayCopy[randomIndex],
arrayCopy[i],
];
}

// Return the first `subsetSize` elements of the shuffled array
return arrayCopy.slice(0, subsetSize);
}

async function seed() {
try {
// Clear existing data from all collections
for (const key in COLLECTIONS) {
const collectionId = COLLECTIONS[key as keyof typeof COLLECTIONS];
const documents = await databases.listDocuments(
config.databaseId!,
collectionId!
);
for (const doc of documents.documents) {
await databases.deleteDocument(
config.databaseId!,
collectionId!,
doc.$id
);
}
}

console.log("Cleared all existing data.");

// Seed Agents
const agents = [];
for (let i = 1; i <= 5; i++) {
const agent = await databases.createDocument(
config.databaseId!,
COLLECTIONS.AGENT!,
ID.unique(),
{
name: `Agent ${i}`,
email: `agent${i}@example.com`,
avatar: agentImages[Math.floor(Math.random() * agentImages.length)],
}
);
agents.push(agent);
}
console.log(`Seeded ${agents.length} agents.`);

// Seed Reviews
const reviews = [];
for (let i = 1; i <= 20; i++) {
const review = await databases.createDocument(
config.databaseId!,
COLLECTIONS.REVIEWS!,
ID.unique(),
{
name: `Reviewer ${i}`,
avatar: reviewImages[Math.floor(Math.random() * reviewImages.length)],
review: `This is a review by Reviewer ${i}.`,
rating: Math.floor(Math.random() * 5) + 1, // Rating between 1 and 5
}
);
reviews.push(review);
}
console.log(`Seeded ${reviews.length} reviews.`);

// Seed Galleries
const galleries = [];
for (const image of galleryImages) {
const gallery = await databases.createDocument(
config.databaseId!,
COLLECTIONS.GALLERY!,
ID.unique(),
{ image }
);
galleries.push(gallery);
}

console.log(`Seeded ${galleries.length} galleries.`);

// Seed Properties
for (let i = 1; i <= 20; i++) {
const assignedAgent = agents[Math.floor(Math.random() * agents.length)];

const assignedReviews = getRandomSubset(reviews, 5, 7); // 5 to 7 reviews
const assignedGalleries = getRandomSubset(galleries, 3, 8); // 3 to 8 galleries

const selectedFacilities = facilities
.sort(() => 0.5 - Math.random())
.slice(0, Math.floor(Math.random() * facilities.length) + 1);

const image =
propertiesImages.length - 1 >= i
? propertiesImages[i]
: propertiesImages[
Math.floor(Math.random() * propertiesImages.length)
];

const property = await databases.createDocument(
config.databaseId!,
COLLECTIONS.PROPERTY!,
ID.unique(),
{
name: `Property ${i}`,
type: propertyTypes[Math.floor(Math.random() * propertyTypes.length)],
description: `This is the description for Property ${i}.`,
address: `123 Property Street, City ${i}`,
geolocation: `192.168.1.${i}, 192.168.1.${i}`,
price: Math.floor(Math.random() * 9000) + 1000,
area: Math.floor(Math.random() * 3000) + 500,
bedrooms: Math.floor(Math.random() * 5) + 1,
bathrooms: Math.floor(Math.random() * 5) + 1,
rating: Math.floor(Math.random() * 5) + 1,
facilities: selectedFacilities,
image: image,
agent: assignedAgent.$id,
reviews: assignedReviews.map((review) => review.$id),
gallery: assignedGalleries.map((gallery) => gallery.$id),
}
);

console.log(`Seeded property: ${property.name}`);
}

console.log("Data seeding completed.");
} catch (error) {
console.error("Error seeding data:", error);
}
}

export default seed;
```

lib/useAppwrite.ts

```ts
import { Alert } from "react-native";
import { useEffect, useState, useCallback } from "react";

interface UseAppwriteOptions> {
fn: (params: P) => Promise;
params?: P;
skip?: boolean;
}

interface UseAppwriteReturn {
data: T | null;
loading: boolean;
error: string | null;
refetch: (newParams: P) => Promise;
}

export const useAppwrite = >({
fn,
params = {} as P,
skip = false,
}: UseAppwriteOptions): UseAppwriteReturn => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(!skip);
const [error, setError] = useState(null);

const fetchData = useCallback(
async (fetchParams: P) => {
setLoading(true);
setError(null);

try {
const result = await fn(fetchParams);
setData(result);
} catch (err: unknown) {
const errorMessage =
err instanceof Error ? err.message : "An unknown error occurred";
setError(errorMessage);
Alert.alert("Error", errorMessage);
} finally {
setLoading(false);
}
},
[fn]
);

useEffect(() => {
if (!skip) {
fetchData(params);
}
}, []);

const refetch = async (newParams: P) => await fetchData(newParams);

return { data, loading, error, refetch };
};
```

## πŸ”— Assets

Assets and Constants used in the project can be found [here](https://drive.google.com/file/d/1HxuvAeJfiPfcZ1co5fU0ahKRw4sbA0gO/view?usp=sharing)

Appwrite Database Setup can be found [here](https://jsmastery.notion.site/Database-Setup-16260f3cbaf3807f8fb6cbed8d1e84fd)

## 🀝 Acknowledgments

Adrian Hajdin: For the comprehensive tutorial and
guidance. [JavaScript Mastery](https://www.youtube.com/watch?v=kt0FrkQgw8w&t=3910s&ab_channel=JavaScriptMastery).

## πŸ“„ License

This project is licensed under the MIT License.

Note: This project is for educational purposes and is free to use under the terms of the MIT License.

## πŸš€ More

**Advance your skills with JSM's Next.js Pro Course**

Enjoyed creating this project? Dive deeper into JSM's 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