@mhmdhammoud/meritt-dev-sdk
v0.1.0
Published
SDK for Meritt Engine content management with Firebase integration
Maintainers
Readme
@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 [email protected].
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
