Overview
The Store Queries API provides functions for retrieving store information, primarily for authenticated store owners to access their own store data. This is the primary way stores get their profile information for dashboard and management interfaces. Location:convex/stores/queries.ts
Get Store by Authenticated Owner
Retrieves complete store information for the authenticated store owner.Copy
const store = await convex.query(api.stores.queries.getStoreByAuthenticatedOwner);
Copy
{
"_id": "j123456789",
"name": "Mario's Pizza Palace",
"primaryCategory": "k123456789",
"status": "approved",
"statusReason": "All documents verified and approved",
"logoUrl": "https://storage.convex.dev/logo123.jpg",
"coverImageUrl": "https://storage.convex.dev/cover456.jpg",
"tradeLicenseUrl": "https://storage.convex.dev/license789.pdf",
"ownerUserId": "user_123456789",
"ownerName": "Mario Rossi",
"ownerEmail": "[email protected]",
"settings": {
"deliveryEnabled": true,
"pickupEnabled": true,
"storeLive": true
},
"bankAccount": {
"IBAN": "AE123456789012345678901",
"bankName": "Emirates NBD",
"bankId": "b123456789",
"accountHolderName": "Mario Rossi"
},
"category": {
"id": "k123456789",
"name": "Food & Restaurants"
},
"address": {
"fullAddress": "123 Main Street, Business Bay, Dubai, UAE",
"city": {
"_id": "c123456789",
"name": "Dubai",
"nameArabic": "دبي"
},
"area": {
"_id": "a987654321",
"name": "Business Bay",
"city": "c123456789"
},
"flatVilaNumber": "Villa 123",
"buildingNameNumber": "Marina Plaza",
"landmark": "Near Dubai Mall",
"latitude": "25.1972",
"longitude": "55.2744"
},
"wallet": {
"availableBalance": 1250.50,
"pendingBalance": 340.25,
"totalBalance": 1590.75
},
"stats": {
"totalProducts": 25,
"activeProducts": 23,
"totalOrders": 150,
"averageRating": 4.8
},
"_creationTime": 1640995200000,
"lastUpdated": 1640995800000
}
Store Dashboard Hook
Complete React hook for store dashboard data:Copy
import { useQuery } from 'convex/react';
import { api } from './convex/_generated/api';
interface StoreData {
store: any;
isLoading: boolean;
error: string | null;
isApproved: boolean;
isPending: boolean;
isRejected: boolean;
}
function useStoreData(): StoreData {
const store = useQuery(api.stores.queries.getStoreByAuthenticatedOwner);
const isLoading = store === undefined;
const error = store === null ? "No store found for this user" : null;
const isApproved = store?.status === "approved";
const isPending = store?.status === "pending";
const isRejected = store?.status === "rejected";
return {
store,
isLoading,
error,
isApproved,
isPending,
isRejected
};
}
// Usage in component
function StoreDashboard() {
const { store, isLoading, error, isApproved, isPending, isRejected } = useStoreData();
if (isLoading) {
return <div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-2">Loading store...</span>
</div>;
}
if (error) {
return <div className="bg-red-50 p-4 rounded-lg">
<p className="text-red-700">{error}</p>
<button className="mt-2 bg-blue-600 text-white px-4 py-2 rounded">
Register Store
</button>
</div>;
}
if (isPending) {
return <div className="bg-yellow-50 p-6 rounded-lg">
<h2 className="text-xl font-bold text-yellow-900 mb-2">Application Under Review</h2>
<p className="text-yellow-700 mb-4">
Your store application is being reviewed by our team. This typically takes 24-48 hours.
</p>
<div className="bg-white p-4 rounded border">
<h3 className="font-medium mb-2">Submitted Information:</h3>
<ul className="text-sm text-gray-600 space-y-1">
<li>• Store Name: {store.name}</li>
<li>• Category: {store.category?.name}</li>
<li>• Trade License: Uploaded</li>
<li>• Submitted: {new Date(store._creationTime).toLocaleDateString()}</li>
</ul>
</div>
</div>;
}
if (isRejected) {
return <div className="bg-red-50 p-6 rounded-lg">
<h2 className="text-xl font-bold text-red-900 mb-2">Application Rejected</h2>
<p className="text-red-700 mb-4">{store.statusReason}</p>
<button className="bg-blue-600 text-white px-4 py-2 rounded">
Update Application
</button>
</div>;
}
// Approved store dashboard
return (
<div className="space-y-6">
{/* Store Header */}
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center space-x-4">
{store.logoUrl && (
<img
src={store.logoUrl}
alt={store.name}
className="w-16 h-16 rounded-lg object-cover"
/>
)}
<div>
<h1 className="text-2xl font-bold text-gray-900">{store.name}</h1>
<p className="text-gray-600">{store.category?.name}</p>
<div className="flex items-center space-x-4 mt-2">
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
store.settings?.storeLive
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}>
{store.settings?.storeLive ? 'Live' : 'Offline'}
</span>
<span className="text-sm text-gray-500">
⭐ {store.stats?.averageRating || 0} rating
</span>
</div>
</div>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white p-4 rounded-lg shadow">
<p className="text-sm text-gray-600">Total Products</p>
<p className="text-2xl font-bold text-gray-900">{store.stats?.totalProducts || 0}</p>
</div>
<div className="bg-white p-4 rounded-lg shadow">
<p className="text-sm text-gray-600">Active Products</p>
<p className="text-2xl font-bold text-green-600">{store.stats?.activeProducts || 0}</p>
</div>
<div className="bg-white p-4 rounded-lg shadow">
<p className="text-sm text-gray-600">Total Orders</p>
<p className="text-2xl font-bold text-blue-600">{store.stats?.totalOrders || 0}</p>
</div>
<div className="bg-white p-4 rounded-lg shadow">
<p className="text-sm text-gray-600">Average Rating</p>
<p className="text-2xl font-bold text-yellow-600">
{store.stats?.averageRating ? store.stats.averageRating.toFixed(1) : '0.0'}
</p>
</div>
</div>
{/* Store Settings */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">Store Settings</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex items-center justify-between p-3 bg-gray-50 rounded">
<span>Delivery Service</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${
store.settings?.deliveryEnabled
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{store.settings?.deliveryEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded">
<span>Pickup Service</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${
store.settings?.pickupEnabled
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{store.settings?.pickupEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded">
<span>Store Status</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${
store.settings?.storeLive
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}>
{store.settings?.storeLive ? 'Live' : 'Offline'}
</span>
</div>
</div>
</div>
{/* Bank Account Info */}
{store.bankAccount && (
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">Bank Account</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-600">Bank Name</p>
<p className="font-medium">{store.bankAccount.bankName}</p>
</div>
<div>
<p className="text-sm text-gray-600">Account Holder</p>
<p className="font-medium">{store.bankAccount.accountHolderName}</p>
</div>
<div className="md:col-span-2">
<p className="text-sm text-gray-600">IBAN</p>
<p className="font-mono text-sm bg-gray-50 p-2 rounded">
{store.bankAccount.IBAN}
</p>
</div>
</div>
</div>
)}
</div>
);
}
export { useStoreData, StoreDashboard };
Store Status Indicators
Visual indicators for different store states:Pending Review
Store application submitted and waiting for admin review
Approved
Store is approved and can start selling products
Rejected
Store application rejected with reason provided
Live
Store is live and accepting orders from customers
Store Information Structure
The store query returns comprehensive information:Copy
interface StoreData {
// Basic Information
_id: string;
name: string;
primaryCategory: string;
status: 'pending' | 'approved' | 'rejected';
statusReason?: string;
// Media Assets
logoUrl?: string;
coverImageUrl?: string;
tradeLicenseUrl: string;
// Owner Information
ownerUserId: string;
ownerName: string;
ownerEmail: string;
// Operational Settings
settings: {
deliveryEnabled: boolean;
pickupEnabled: boolean;
storeLive: boolean;
};
// Financial Information
bankAccount?: {
IBAN: string;
bankName: string;
bankId: string;
accountHolderName: string;
};
// Address Information (enriched with city/area details)
address?: {
fullAddress?: string;
city?: {
_id: string;
name: string;
nameArabic: string;
};
area?: {
_id: string;
name: string;
city: string;
};
flatVilaNumber?: string;
buildingNameNumber?: string;
landmark?: string;
latitude?: string;
longitude?: string;
};
// Wallet Information
wallet: {
availableBalance: number;
pendingBalance: number;
totalBalance: number;
};
// Category Information
category: {
id: string;
name: string;
};
// Statistics
stats: {
totalProducts: number;
activeProducts: number;
totalOrders: number;
averageRating: number;
};
// Timestamps
_creationTime: number;
lastUpdated: number;
}
Get Store Address
Retrieve only the address information for a store with enriched city and area details.Optional store ID. If not provided, returns address for authenticated owner’s store.
Copy
// Get address for authenticated store owner
const storeAddress = await convex.query(api.stores.queries.getStoreAddress);
// Get address for specific store (must be owned by authenticated user)
const specificStoreAddress = await convex.query(api.stores.queries.getStoreAddress, {
storeId: "j123456789"
});
Copy
{
"_id": "j123456789",
"address": {
"fullAddress": "123 Main Street, Business Bay, Dubai, UAE",
"city": {
"_id": "c123456789",
"name": "Dubai",
"nameArabic": "دبي"
},
"area": {
"_id": "a987654321",
"name": "Business Bay",
"city": "c123456789"
},
"flatVilaNumber": "Villa 123",
"buildingNameNumber": "Marina Plaza",
"landmark": "Near Dubai Mall",
"latitude": "25.1972",
"longitude": "55.2744"
}
}
Returns
null if the authenticated user doesn’t have a registered store and no storeId is provided.Get Store Orders
Retrieve orders for the authenticated store with filtering and pagination support.Filter orders by status: “pending”, “confirmed”, “preparing”, “ready”, “out_for_delivery”, “delivered”, “cancelled”
Maximum number of orders to return (default: 50)
Copy
// Get all orders
const allOrders = await convex.query(api.stores.queries.getStoreOrders);
// Get only pending orders
const pendingOrders = await convex.query(api.stores.queries.getStoreOrders, {
status: "pending"
});
// Get confirmed orders with limit
const confirmedOrders = await convex.query(api.stores.queries.getStoreOrders, {
status: "confirmed",
limit: 20
});
Copy
[
{
"_id": "o123456789",
"_creationTime": 1640995200000,
"customerId": "cu123456789",
"customerName": "Ahmed Al-Mansouri",
"customerPhone": "+971501234567",
"deliveryAddress": {
"fullAddress": "Building 23, Street 45, Al Karama, Dubai",
"city": "Dubai",
"area": "Al Karama",
"flatVilaNumber": "Apt 304",
"buildingNameNumber": "Golden Tower",
"phoneNumber": "+971501234567",
"landmark": "Near Karama Park"
},
"subtotal": 125.50,
"deliveryFee": 15.00,
"totalAmount": 140.50,
"storeEarnings": 112.40,
"status": "pending",
"paymentStatus": "paid",
"orderDate": 1640995200000,
"itemsCount": 3,
"deliveryType": "delivery",
"orderItems": [
{
"productId": "p123456789",
"productName": "Margherita Pizza",
"productImageUrl": "https://storage.convex.dev/pizza123.jpg",
"quantity": 2,
"unitPrice": 45.00,
"totalPrice": 90.00
},
{
"productId": "p987654321",
"productName": "Caesar Salad",
"productImageUrl": "https://storage.convex.dev/salad456.jpg",
"quantity": 1,
"unitPrice": 35.50,
"totalPrice": 35.50
}
]
}
]
Real-time Updates
The store query automatically updates when store information changes:Copy
// Store data updates automatically
function StoreProfile() {
const store = useQuery(api.stores.queries.getStoreByAuthenticatedOwner);
// This will re-render when:
// - Store status changes (approved/rejected)
// - Store settings are updated
// - Bank account information is added/updated
// - Store statistics change
return (
<div>
{store && (
<div>
<h1>{store.name}</h1>
<p>Status: {store.status}</p>
<p>Live: {store.settings.storeLive ? 'Yes' : 'No'}</p>
</div>
)}
</div>
);
}
Error Handling
No store found
No store found
Response:
nullThis indicates the authenticated user doesn’t have a registered store yet.Copy
if (store === null) {
// User needs to register a store
return <RegisterStoreComponent />;
}
Authentication required
Authentication required
Status Code:
401Copy
{
"error": "Authentication required to access store data"
}
Store access denied
Store access denied
Status Code:
403Copy
{
"error": "Access denied. User is not the owner of this store."
}
Best Practices
Real-time Updates
Use the React hook pattern to automatically receive updates when store data changes
Status Handling
Always check store status before showing management features
Loading States
Handle loading states gracefully while store data is being fetched
Error Boundaries
Implement proper error handling for missing or inaccessible stores
This query only returns data for the authenticated user’s own store. Store owners cannot access other stores’ private information.
Use the store status to conditionally show different UI components - registration forms for new users, pending status for under review, and full dashboard for approved stores.
