@mhmdhammoud/meritt-dev-sdk v0.1.0
@meritt-dev/sdk
A powerful SDK for Meritt Engine content management with Firebase integration. This SDK provides a seamless interface for managing content, media, and more in your Meritt Engine projects.
Features
- ✅ Direct Firebase integration
- ✅ Type-safe API with TypeScript
- ✅ React hooks for easy integration
- ✅ Zustand stores for state management
- ✅ Comprehensive content types (Products, Articles, Portfolio Projects)
- ✅ Media management with upload progress
- ✅ Built-in pagination and filtering
- ✅ Provider-based architecture for clean integration
Installation
# Using npm
npm install @meritt-dev/sdk
# Using yarn
yarn add @meritt-dev/sdk
# Using pnpm
pnpm add @meritt-dev/sdkPrerequisites
The SDK requires Firebase as a peer dependency, and React for hooks functionality.
# For React integration
npm install react react-dom
# Firebase is included as a dependencyQuick Start
Setup with Provider
Wrap your application with the MerittProvider at the root level:
import { MerittProvider } from '@meritt-dev/sdk';
function App() {
return (
<MerittProvider
config={{
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET!,
messagingSenderId:
process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID!,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID!,
}}
>
<YourAppContent />
</MerittProvider>
);
}Using Content Hooks
Products
import { useProducts } from '@meritt-dev/sdk';
function ProductsList() {
const { products, loading, error, loadMore } = useProducts({
featured: true,
limit: 10,
});
if (loading) return <div>Loading products...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Featured Products</h2>
<div className="grid">
{products.map((product) => (
<div key={product.id} className="product-card">
<img src={product.images[0]} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
</div>
))}
</div>
<button onClick={loadMore}>Load More</button>
</div>
);
}Media Management
Use the useMedia hook to fetch and manage media assets:
import { useMedia } from '@meritt-dev/sdk';
function MediaGallery() {
const { media, loading, uploadMedia, isUploading, progress } = useMedia({
fileType: 'image',
limit: 20,
});
const handleFileChange = async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await uploadMedia(file, {
folder: 'products',
alt: 'Product image',
});
console.log('Uploaded successfully:', result.downloadUrl);
} catch (error) {
console.error('Upload failed:', error);
}
}
};
return (
<div>
<h2>Media Gallery</h2>
<div className="upload-section">
<input type="file" onChange={handleFileChange} />
{isUploading && (
<div className="progress-bar">
<div className="progress" style={{ width: `${progress}%` }}></div>
<span>{progress.toFixed(0)}%</span>
</div>
)}
</div>
<div className="media-grid">
{media.map((item) => (
<div key={item.id} className="media-item">
<img src={item.thumbnailUrl} alt={item.alt || item.fileName} />
<p>{item.fileName}</p>
</div>
))}
</div>
</div>
);
}Direct File Upload
If you only need file upload functionality without creating media records:
import { useUpload } from '@meritt-dev/sdk';
function FileUploader() {
const { uploadFile, isUploading, progress, downloadUrl } = useUpload();
const handleFileChange = async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await uploadFile(file, { folder: 'documents' });
console.log('Download URL:', result.downloadUrl);
} catch (error) {
console.error('Upload error:', error);
}
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
{isUploading && <progress value={progress} max="100" />}
{downloadUrl && <div>File uploaded: {downloadUrl}</div>}
</div>
);
}API Reference
Provider
MerittProvider
The main provider component that initializes Firebase and provides context to all child components.
<MerittProvider
config={{
apiKey: string,
authDomain: string,
projectId: string,
storageBucket: string,
messagingSenderId: string,
appId: string,
}}
namespace?: string // Optional namespace for multiple instances
>
<App />
</MerittProvider>useMerittContext()
Hook to access the Meritt context within components.
const {
config, // Firebase configuration
namespace, // Optional namespace
isInitialized, // Boolean indicating if Firebase is ready
firebase, // Firebase instances (db, auth, storage)
error, // Initialization error if any
} = useMerittContext();useFirebaseInstances()
Hook to directly access Firebase instances.
const {
db, // Firestore database
auth, // Firebase Auth
storage, // Firebase Storage
} = useFirebaseInstances();Content Hooks
useProducts(options?)
const {
products, // Array of products
loading, // Boolean loading state
error, // Error object if any
fetchProduct, // Function to fetch a single product
loadMore, // Function to load more products
createProduct, // Function to create a new product
updateProduct, // Function to update a product
deleteProduct, // Function to delete a product
} = useProducts({
featured: boolean, // Filter by featured status
inStock: boolean, // Filter by stock status
categoryId: string, // Filter by category ID
limit: number, // Number of items to fetch
offset: number, // Offset for pagination
});useMedia(options?)
const {
media, // Array of media items
loading, // Boolean loading state
error, // Error object if any
// Upload state
isUploading, // Boolean upload state
progress, // Upload progress (0-100)
downloadUrl, // URL of the last uploaded file
uploadError, // Upload error if any
// Operations
uploadMedia, // Function to upload a file and create a media record
getMediaById, // Function to fetch a single media item
loadMore, // Function to load more media items
updateMedia, // Function to update a media item
deleteMedia, // Function to delete a media item
resetUploadState, // Function to reset upload state
} = useMedia({
fileType?: string | string[], // Filter by file type
folder?: string, // Filter by folder
tags?: string[], // Filter by tags
limit?: number, // Number of items to fetch
offset?: number, // Offset for pagination
});useUpload()
const {
isUploading, // Boolean upload state
progress, // Upload progress (0-100)
downloadUrl, // URL of the last uploaded file
error, // Upload error if any
uploadFile, // Function to upload a file
resetUploadState, // Function to reset upload state
} = useUpload();Type Definitions
The SDK exports TypeScript interfaces for all content types:
Product- E-commerce productArticle- Blog articlePortfolioProject- Portfolio projectMedia- Media assetCategory- Content categoryOrder- E-commerce order
Refer to the Types Documentation for detailed type definitions.
Advanced Usage
Multiple Instances
You can use multiple provider instances for multi-tenant applications:
import { MerittProvider } from '@meritt-dev/sdk';
function MultiTenantApp() {
return (
<div>
<MerittProvider config={tenant1Config} namespace="tenant1">
<TenantDashboard tenantId="tenant1" />
</MerittProvider>
<MerittProvider config={tenant2Config} namespace="tenant2">
<TenantDashboard tenantId="tenant2" />
</MerittProvider>
</div>
);
}Using Stores Directly
You can use the Zustand stores directly for more advanced use cases:
import {
useProductStore,
useMediaStore,
useFirebaseInstances,
} from '@meritt-dev/sdk';
function AdvancedComponent() {
const firebase = useFirebaseInstances();
const products = useProductStore((state) => state.products);
const fetchProducts = useProductStore((state) => state.fetchProducts);
// Use store methods with Firebase instances
const loadProducts = async () => {
await fetchProducts({ limit: 20 }, firebase);
};
return <div>Advanced component content</div>;
}Custom Firebase Queries
For advanced use cases, you can access the Firebase instances directly:
import { useFirebaseInstances } from '@meritt-dev/sdk';
import { collection, query, where, getDocs } from 'firebase/firestore';
function CustomQueryComponent() {
const { db } = useFirebaseInstances();
const customQuery = async () => {
const productsRef = collection(db, 'products');
const q = query(productsRef, where('price', '<', 50));
const snapshot = await getDocs(q);
// Process results...
};
return <div>Custom query component</div>;
}Testing
For testing, you can provide mock Firebase configurations:
import { MerittProvider } from '@meritt-dev/sdk';
function TestWrapper({ children }) {
const mockConfig = {
apiKey: 'test-api-key',
authDomain: 'test.firebaseapp.com',
projectId: 'test-project',
storageBucket: 'test-project.appspot.com',
messagingSenderId: '123456789',
appId: 'test-app-id',
};
return <MerittProvider config={mockConfig}>{children}</MerittProvider>;
}Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
If you have any questions or need help integrating the SDK, please open an issue on the GitHub repository or contact us at support@meritt.dev.
Firestore Indexes
The SDK requires specific Firestore indexes for optimal query performance. A firestore.indexes.json file is included with the SDK.
Setup
Copy the indexes file to your Firebase project root:
cp node_modules/@meritt-dev/sdk/firestore.indexes.json .Deploy the indexes:
firebase deploy --only firestore:indexesWait for indexes to build (usually 2-5 minutes)
Prerequisites
- Firebase CLI installed:
npm install -g firebase-tools - Firebase project initialized:
firebase init - Logged in to Firebase:
firebase login
Troubleshooting
If you get index-related errors:
- Ensure the
firestore.indexes.jsonfile is in your project root - Check the Firebase Console for index build status
- Wait for indexes to finish building before testing queries
5 months ago