This content originally appeared on DEV Community and was authored by Cathy Lai
Introduction
In this tutorial, I’ll walk you through how I built a complete e-commerce vegetable store from scratch using Lovable. What started as a simple idea turned into a fully functional online store with shopping cart, payment processing, user authentication, and order management—all built in record time.
By the end of this guide, you’ll understand how to leverage modern web technologies to create your own e-commerce platform without writing backend code from scratch.
What We Built
Our vegetable e-commerce store includes:
Shopping Cart System with add/remove functionality
Stripe Payment Integration for secure checkout
User Authentication (sign up/login)
Order Management with persistent order history
Lovable Cloud Backend for database and authentication
GitHub Integration for version control
Tech Stack
- Frontend: React 18 + TypeScript + Vite
- Styling: Tailwind CSS with custom design system
- UI Components: Radix UI + shadcn/ui
- State Management: React Context API
- Backend: Lovable Cloud (Supabase-powered)
- Payments: Stripe
- Routing: React Router v6
Part 1: Setting Up the Foundation
The Product Catalog
First, I created a simple product data structure in src/data/products.ts:
export const products = [
{
id: 1,
name: "Fresh Tomatoes",
price: 4.99,
image: "/src/assets/tomatoes.jpg",
description: "Ripe and juicy organic tomatoes"
},
// ... more products
];
This approach keeps our product data centralized and easy to manage. For a production app, you’d fetch this from a database, but starting with static data helps you prototype quickly.
Building the Product Display
I created a reusable ProductCard component that displays each product with an image, price, and “Add to Cart” button. Key features:
- Responsive design using Tailwind CSS
- Hover effects for better UX
- Integration with the shopping cart context
<Card className="overflow-hidden hover:shadow-lg transition-shadow">
<img src={product.image} alt={product.name} />
<CardContent>
<h3>{product.name}</h3>
<p>${product.price}</p>
<Button onClick={() => addToCart(product)}>Add to Cart</Button>
</CardContent>
</Card>
Part 2: Implementing the Shopping Cart
Cart Context Setup
The shopping cart is managed through React Context (src/contexts/CartContext.tsx), making cart state accessible throughout the app:
const CartContext = createContext<CartContextType | undefined>(undefined);
export const CartProvider = ({ children }: { children: ReactNode }) => {
const [cartItems, setCartItems] = useState<CartItem[]>([]);
const addToCart = (product: Product) => {
// Logic to add or increment quantity
};
const removeFromCart = (productId: number) => {
// Logic to remove or decrement quantity
};
return (
<CartContext.Provider value={{ cartItems, addToCart, removeFromCart, ... }}>
{children}
</CartContext.Provider>
);
};
Cart Features
The cart includes:
- Quantity Management: Increment/decrement items
- Automatic Calculations: Total price updated in real-time
- Persistent State: Cart survives page refreshes (localStorage)
- Visual Feedback: Toast notifications when items are added
Cart Page
The dedicated cart page (src/pages/Cart.tsx) shows:
- All items in the cart with images and prices
- Quantity controls for each item
- Running total calculation
- Checkout button that leads to payment
Part 3: Stripe Payment Integration
Setting Up Stripe
Integrating Stripe for payments involved several components:
-
Backend Edge Function (
supabase/functions/create-checkout/index.ts):- Creates Stripe checkout sessions
- Handles payment intent creation
- Associates orders with authenticated users
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: items.map(item => ({
price_data: {
currency: 'usd',
product_data: { name: item.name },
unit_amount: Math.round(item.price * 100),
},
quantity: item.quantity,
})),
mode: 'payment',
success_url: `${origin}/orders`,
cancel_url: `${origin}/cart`,
});
-
Frontend Checkout Page (
src/pages/Checkout.tsx):- Integrates Stripe Elements
- Handles payment form submission
- Provides loading states and error handling
-
Payment Confirmation (
supabase/functions/confirm-payment/index.ts):- Verifies payment status
- Creates order records in the database
- Returns order confirmation to the user
The Checkout Flow
- User clicks “Proceed to Checkout” from cart
- Cart items are sent to the backend
- Stripe checkout session is created
- User enters payment details
- Payment is processed securely by Stripe
- Order is confirmed and saved to database
- User is redirected to orders page
Part 4: Lovable Cloud Backend
Why Lovable Cloud?
Instead of manually setting up a backend server, database, and authentication system, Lovable Cloud provides everything out of the box. It’s powered by Supabase but fully integrated into the Lovable platform.
Database Setup
I created an orders table using a database migration:
CREATE TABLE public.orders (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
items JSONB NOT NULL,
total DECIMAL(10,2) NOT NULL,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Enable Row Level Security
ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
-- Users can only see their own orders
CREATE POLICY "Users can view own orders"
ON public.orders
FOR SELECT
USING (auth.uid() = user_id);
Row Level Security (RLS)
RLS policies ensure users can only access their own orders—critical for security and privacy. The policy checks that the authenticated user’s ID matches the user_id in the order record.
Part 5: User Authentication
Authentication Context
User authentication is managed through AuthContext (src/contexts/AuthContext.tsx):
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// Check current session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
});
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user, signIn, signUp, signOut }}>
{children}
</AuthContext.Provider>
);
};
Sign Up & Login Pages
I created dedicated pages for authentication:
Sign Up (src/pages/Signup.tsx):
- Email and password registration
- Form validation
- Automatic redirect after successful signup
Login (src/pages/Login.tsx):
- Email and password authentication
- “Remember me” functionality
- Password reset option
Auto-Confirm Email Setup
For development and testing, I enabled auto-confirm for email signups so users don’t need to verify their email before accessing the app. This can be adjusted for production environments.
Protected Routes
The navigation automatically shows/hides features based on authentication status:
{user ? (
<>
<Link to="/orders">My Orders</Link>
<Button onClick={signOut}>Logout</Button>
</>
) : (
<>
<Link to="/login">Login</Link>
<Link to="/signup">Sign Up</Link>
</>
)}
Part 6: Order Management
Orders Page
The Orders page (src/pages/Orders.tsx) fetches and displays a user’s order history:
useEffect(() => {
const fetchOrders = async () => {
const { data, error } = await supabase
.from('orders')
.select('*')
.order('created_at', { ascending: false });
if (data) setOrders(data);
};
if (user) fetchOrders();
}, [user]);
Each order shows:
- Order ID and date
- Items purchased with quantities
- Total amount paid
- Order status (pending, completed, etc.)
Order Creation Flow
When a payment is confirmed:
- Backend function verifies Stripe payment
- Order record is created in database
- User ID is attached to the order
- User can view order in their order history
Part 7: Design System
Tailwind Configuration
I set up a comprehensive design system using Tailwind CSS with custom semantic tokens defined in src/index.css:
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 142 76% 36%;
--primary-foreground: 355.7 100% 97.3%;
/* ... more tokens */
}
Benefits of Semantic Tokens
Instead of using colors directly (like bg-white or text-black), semantic tokens provide:
- Consistent theming across the entire app
- Easy dark mode implementation
- Centralized design control
- Better maintainability
Component Styling
All components use these semantic tokens:
<Button className="bg-primary text-primary-foreground hover:bg-primary/90">
Add to Cart
</Button>
Part 8: Connecting to GitHub
Why GitHub Integration?
Connecting your Lovable project to GitHub provides:
- Version Control: Track all changes to your codebase
- Collaboration: Work with team members
- Backup: Automatic code backup
- Deployment Options: Deploy to various hosting platforms
- Professional Workflow: Industry-standard development practices
How to Connect
Connecting to GitHub is straightforward:
- Click the GitHub Button: Located in the top-right of the Lovable editor
- Authorize Lovable: Grant permissions to the Lovable GitHub App
- Create Repository: Choose a repository name and visibility (public/private)
- Automatic Sync: Code syncs bidirectionally between Lovable and GitHub
Bidirectional Sync
Once connected:
- Changes in Lovable automatically push to GitHub
- Changes in GitHub can be pulled into Lovable
- Commit history is preserved
- Branch management is supported
Development Workflow
With GitHub connected, you can:
- Clone locally: Work in your favorite IDE (VS Code, WebStorm, etc.)
- Create branches: Experiment with features safely
- Review changes: Use GitHub’s diff viewer
- Collaborate: Invite team members to contribute
- Deploy easily: Connect to Vercel, Netlify, or other platforms
Key Takeaways
What I Learned
- Start Simple: Begin with static data and basic features, then iterate
- Context is Powerful: React Context handles state management elegantly for small-to-medium apps
- Backend-as-a-Service: Lovable Cloud eliminates backend setup complexity
- Security First: RLS policies protect user data automatically
- Design Systems Matter: Semantic tokens make styling consistent and maintainable
Performance Considerations
- Lazy Loading: Images load on-demand for better performance
- React Query: Could be added for better data fetching and caching
- Code Splitting: Vite handles this automatically with dynamic imports
Conclusion
Building a full-stack e-commerce site doesn’t have to be overwhelming. With modern tools like Lovable, you can focus on creating value for your users rather than wrestling with infrastructure.
The combination of React for the frontend, Lovable Cloud for the backend, and Stripe for payments provides a solid foundation that can scale from prototype to production.
Whether you’re building your first e-commerce site or your tenth, the patterns and practices in this tutorial will help you ship faster and more confidently.
Resources
- Lovable Documentation: https://docs.lovable.dev/
- Stripe Documentation: https://stripe.com/docs
Built with
using Lovable
This content originally appeared on DEV Community and was authored by Cathy Lai