Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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.
- Host: GitHub
- URL: https://github.com/r3ss1/real-estate-app
- Owner: r3ss1
- License: mit
- Created: 2024-12-21T22:59:23.000Z (30 days ago)
- Default Branch: main
- Last Pushed: 2025-01-01T23:03:39.000Z (19 days ago)
- Last Synced: 2025-01-02T00:18:46.373Z (18 days ago)
- Topics: appwrite, expo, nativewind, react-native, tailwindcss, typescript
- Language: TypeScript
- Homepage:
- Size: 903 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
A Real Estate App
In this project, I am coding along with Adrian Hajdin from JavaScript Mastery (YouTube) to build a real estate application.
Initiated
Completed
Progress - 29Dec24
YT video lenght
December 2024
Est. ~ mid 2025
2%
3 hours
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.
- Expo
- React Native
- TypeScript
- Nativewind
- Appwrite
- Tailwind CSSπ **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
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 ExpoYou can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
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 galleriesconst 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!