InstaGoods follows a component-based architecture with clear separation between customer-facing and supplier portal components.
src/components/
├── customer/ # Customer-facing components
│ ├── CategoryNav.tsx
│ ├── FreelanceCard.tsx
│ ├── GroceryCard.tsx
│ ├── Header.tsx
│ ├── ProductCard.tsx
│ ├── ServiceCard.tsx
│ └── ServiceRequestForm.tsx
├── supplier/ # Supplier portal components
│ └── SupplierNav.tsx
└── ui/ # Reusable UI components (shadcn)
├── accordion.tsx
├── alert.tsx
├── button.tsx
├── card.tsx
├── dialog.tsx
├── input.tsx
└── ... (50+ components)
File: src/components/customer/Header.tsx
Purpose: Main navigation header for customer-facing pages
Features:
- Responsive design with mobile hamburger menu
- Location selector with autocomplete
- Search functionality
- Cart and wishlist indicators
- "Shop by Business" filter
Props:
// No props - uses contexts internallyUsage:
import Header from "@/components/customer/Header";
function Page() {
return <Header />;
}File: src/components/customer/CategoryNav.tsx
Purpose: Category navigation for filtering products
Features:
- Grid layout for category selection
- Active state management
- Responsive design
Props:
interface CategoryNavProps {
activeCategory: string;
onCategoryChange: (category: string) => void;
}Usage:
<CategoryNav
activeCategory={category}
onCategoryChange={setCategory}
/>Files: src/components/customer/[Type]Card.tsx
Purpose: Display individual items in grid layouts
Features:
- Consistent card design
- Image handling with fallbacks
- Price display
- Add to cart/wishlist buttons
- Location-based availability badges
- Responsive sizing
Common Props:
interface CardProps {
id: string;
name: string;
description: string;
price: number;
image_url?: string;
supplier_name?: string;
supplier_id: string;
latitude?: number;
longitude?: number;
delivery_radius_km?: number;
no_delivery?: boolean;
}Usage:
<ProductCard
id={product.id}
name={product.name}
price={product.price}
image_url={product.image_url}
supplier_name={product.supplier_name}
// ... other props
/>File: src/components/customer/ServiceRequestForm.tsx
Purpose: Form for customers to request services
Features:
- Form validation with Zod
- Date/time selection
- Location input
- Budget estimation
Props:
interface ServiceRequestFormProps {
serviceId: string;
serviceName: string;
supplierId: string;
onClose: () => void;
}File: src/components/supplier/SupplierNav.tsx
Purpose: Navigation bar for supplier portal
Features:
- Dashboard, Products, Orders, Incomes, Expenses, Service Requests, Optimize, Settings links
- Mobile-responsive with hamburger menu
- Sign out functionality
Props:
interface SupplierNavProps {
onSignOut: () => void;
}Usage:
<SupplierNav onSignOut={handleSignOut} />All UI components follow the shadcn/ui pattern and are located in src/components/ui/.
File: src/components/ui/button.tsx
Variants:
default- Primary action buttondestructive- Dangerous actionsoutline- Secondary actionsecondary- Alternative styleghost- Minimal stylelink- Text-only style
Sizes:
default- Standard sizesm- Smalllg- Largeicon- Icon-only button
Usage:
<Button variant="default" size="lg">
Click Me
</Button>File: src/components/ui/card.tsx
Subcomponents:
Card- ContainerCardHeader- Header sectionCardTitle- Title textCardDescription- Subtitle textCardContent- Main contentCardFooter- Footer section
Usage:
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>
Content goes here
</CardContent>
<CardFooter>
Footer content
</CardFooter>
</Card>File: src/components/ui/dialog.tsx
Usage:
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog description</DialogDescription>
</DialogHeader>
{/* Dialog content */}
</DialogContent>
</Dialog>Input:
<Input
type="text"
placeholder="Enter text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>Label:
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />Form (React Hook Form integration):
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(6)
});
function MyForm() {
const form = useForm({
resolver: zodResolver(schema)
});
return (
<Form {...form}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form>
);
}File: src/context/CartContext.tsx
Purpose: Manages shopping cart state
Exported Functions:
interface CartContextType {
cartItems: CartItem[];
addToCart: (item: CartItem) => void;
removeFromCart: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
getCartTotal: () => number;
getCartCount: () => number;
}Usage:
import { useCart } from "@/context/CartContext";
function Component() {
const { addToCart, cartItems } = useCart();
return (
<button onClick={() => addToCart(item)}>
Add to Cart ({cartItems.length})
</button>
);
}File: src/context/LocationContext.tsx
Purpose: Manages user location for delivery filtering
Exported Functions:
interface LocationContextType {
userAddress: string | null;
userCoordinates: { lat: number; lng: number } | null;
setUserLocation: (address: string, coords: { lat: number; lng: number }) => void;
clearUserLocation: () => void;
}Usage:
import { useLocation } from "@/context/LocationContext";
function Component() {
const { userAddress, userCoordinates, setUserLocation } = useLocation();
return (
<div>
Current Location: {userAddress || "Not set"}
</div>
);
}File: src/context/WishlistContext.tsx
Purpose: Manages wishlist state
Exported Functions:
interface WishlistContextType {
wishlistItems: string[]; // Product IDs
addToWishlist: (id: string) => void;
removeFromWishlist: (id: string) => void;
isInWishlist: (id: string) => boolean;
}File: src/hooks/useSupplierAuth.tsx
Purpose: Handle supplier authentication and session
Returns:
interface UseSupplierAuthReturn {
loading: boolean;
supplierId: string | null;
signOut: () => Promise<void>;
}Usage:
import { useSupplierAuth } from "@/hooks/useSupplierAuth";
function SupplierPage() {
const { loading, supplierId, signOut } = useSupplierAuth();
if (loading) return <div>Loading...</div>;
if (!supplierId) return <div>Not authenticated</div>;
return <div>Supplier ID: {supplierId}</div>;
}File: src/hooks/useProduct.tsx
Purpose: Fetch single product data
Returns:
interface UseProductReturn {
product: Product | null;
loading: boolean;
}File: src/hooks/useMarketplaceProducts.tsx
Purpose: Fetch and filter marketplace products
Returns:
interface UseMarketplaceProductsReturn {
products: Product[];
loading: boolean;
error: Error | null;
}Always use Tailwind's responsive modifiers:
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{/* Content */}
</div>Show loading indicators:
if (loading) {
return <div className="flex items-center justify-center p-8">Loading...</div>;
}Handle errors gracefully:
if (error) {
return <Alert variant="destructive">{error.message}</Alert>;
}Always define prop types:
interface MyComponentProps {
title: string;
count: number;
onAction: () => void;
}
function MyComponent({ title, count, onAction }: MyComponentProps) {
// Component logic
}Use semantic HTML and ARIA attributes:
<button aria-label="Add to cart" onClick={handleClick}>
<ShoppingCart className="h-4 w-4" />
</button>import { render, screen } from '@testing-library/react';
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
it('renders product name', () => {
render(
<ProductCard
id="1"
name="Test Product"
price={10}
supplier_id="sup1"
/>
);
expect(screen.getByText('Test Product')).toBeInTheDocument();
});
});- Mount: Component initializes, contexts load
- Render: Component renders based on props/state
- Update: Re-renders when props/state change
- Unmount: Cleanup (remove listeners, cancel requests)
Example with cleanup:
useEffect(() => {
const subscription = subscribeToUpdates();
return () => {
subscription.unsubscribe();
};
}, []);const ExpensiveComponent = memo(({ data }) => {
// Expensive rendering
});const handleClick = useCallback(() => {
// Handler logic
}, [dependencies]);const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);