feat: food ordering on coffee page
- Add 'Place Order' button to coffee page - Order modal with menu item selection by category - Order type options: pickup, dine in, room delivery (+10%) - Room delivery requires login (disabled for guests) - Calculate subtotal, service fee, and total - Special instructions field - Admin receives new orders in database - Create orders and order_items tables
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const user = await getSession();
|
||||
const { items, order_type, room_id, notes } = await request.json();
|
||||
|
||||
// items is array of { menu_item_id, quantity }
|
||||
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||
return NextResponse.json({ error: 'Order items are required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!order_type || !['pickup', 'dine_in', 'room_delivery'].includes(order_type)) {
|
||||
return NextResponse.json({ error: 'Valid order type is required (pickup, dine_in, room_delivery)' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Room delivery requires login
|
||||
if (order_type === 'room_delivery' && !user) {
|
||||
return NextResponse.json({ error: 'Room delivery requires login. Please log in to continue.' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Room delivery requires room_id
|
||||
if (order_type === 'room_delivery' && !room_id) {
|
||||
return NextResponse.json({ error: 'Room selection is required for room delivery' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get menu item prices
|
||||
const menuItemIds = items.map((i: { menu_item_id: number }) => i.menu_item_id);
|
||||
const { rows: menuItems } = await db.query(
|
||||
'SELECT id, name, price FROM menu_items WHERE id = ANY($1)',
|
||||
[menuItemIds]
|
||||
);
|
||||
|
||||
if (menuItems.length !== menuItemIds.length) {
|
||||
return NextResponse.json({ error: 'One or more menu items not found' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Calculate total
|
||||
const priceMap = new Map(menuItems.map((m: { id: number; price: string }) => [m.id, parseFloat(m.price)]));
|
||||
let subtotal = 0;
|
||||
const orderItems = items.map((i: { menu_item_id: number; quantity: number }) => {
|
||||
const price = priceMap.get(i.menu_item_id) || 0;
|
||||
const itemTotal = price * i.quantity;
|
||||
subtotal += itemTotal;
|
||||
return {
|
||||
menu_item_id: i.menu_item_id,
|
||||
quantity: i.quantity,
|
||||
unit_price: price,
|
||||
total_price: itemTotal,
|
||||
};
|
||||
});
|
||||
|
||||
// Room delivery adds 10% service fee
|
||||
const serviceFee = order_type === 'room_delivery' ? subtotal * 0.1 : 0;
|
||||
const total = subtotal + serviceFee;
|
||||
|
||||
// Create order
|
||||
const { rows: orderRows } = await db.query(`
|
||||
INSERT INTO orders (user_id, order_type, room_id, subtotal, service_fee, total, status, notes)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 'pending', $7)
|
||||
RETURNING *
|
||||
`, [user?.id || null, order_type, order_type === 'room_delivery' ? room_id : null, subtotal, serviceFee, total, notes || null]);
|
||||
|
||||
const orderId = orderRows[0].id;
|
||||
|
||||
// Insert order items
|
||||
for (const item of orderItems) {
|
||||
await db.query(`
|
||||
INSERT INTO order_items (order_id, menu_item_id, quantity, unit_price, total_price)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`, [orderId, item.menu_item_id, item.quantity, item.unit_price, item.total_price]);
|
||||
}
|
||||
|
||||
// Get full order with items
|
||||
const { rows: fullOrder } = await db.query(`
|
||||
SELECT o.*,
|
||||
json_agg(json_build_object(
|
||||
'menu_item_id', oi.menu_item_id,
|
||||
'quantity', oi.quantity,
|
||||
'unit_price', oi.unit_price,
|
||||
'total_price', oi.total_price,
|
||||
'name', m.name
|
||||
)) as items
|
||||
FROM orders o
|
||||
LEFT JOIN order_items oi ON o.id = oi.order_id
|
||||
LEFT JOIN menu_items m ON oi.menu_item_id = m.id
|
||||
WHERE o.id = $1
|
||||
GROUP BY o.id
|
||||
`, [orderId]);
|
||||
|
||||
// Get admin notification targets
|
||||
const { rows: admins } = await db.query(
|
||||
'SELECT email, phone FROM users WHERE role = \'admin\' AND (email IS NOT NULL OR phone IS NOT NULL)'
|
||||
);
|
||||
|
||||
// TODO: Send notification to admins (email/SMS)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
order: fullOrder[0],
|
||||
message: order_type === 'room_delivery'
|
||||
? 'Order placed! We\'ll deliver it to your room shortly.'
|
||||
: order_type === 'dine_in'
|
||||
? 'Order placed! We\'ll prepare it for dine-in.'
|
||||
: 'Order placed! We\'ll have it ready for pickup.'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Order error:', error);
|
||||
return NextResponse.json({ error: 'Failed to create order' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const user = await getSession();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const status = searchParams.get('status') || 'all';
|
||||
const limit = parseInt(searchParams.get('limit') || '50');
|
||||
|
||||
let query = `
|
||||
SELECT o.*, r.name as room_name, u.username, u.first_name, u.last_name
|
||||
FROM orders o
|
||||
LEFT JOIN rooms r ON o.room_id = r.id
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
`;
|
||||
|
||||
const params: any[] = [];
|
||||
if (status !== 'all') {
|
||||
query += ' WHERE o.status = $1';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY o.created_at DESC LIMIT $' + (params.length + 1);
|
||||
params.push(limit);
|
||||
|
||||
const { rows } = await db.query(query, params);
|
||||
|
||||
// Get items for each order
|
||||
for (const order of rows) {
|
||||
const { rows: items } = await db.query(`
|
||||
SELECT oi.*, m.name, m.description
|
||||
FROM order_items oi
|
||||
JOIN menu_items m ON oi.menu_item_id = m.id
|
||||
WHERE oi.order_id = $1
|
||||
`, [order.id]);
|
||||
order.items = items;
|
||||
}
|
||||
|
||||
return NextResponse.json({ orders: rows });
|
||||
} catch (error) {
|
||||
console.error('Get orders error:', error);
|
||||
return NextResponse.json({ error: 'Failed to get orders' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
try {
|
||||
const user = await getSession();
|
||||
if (!user || user.role !== 'admin') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { order_id, status } = await request.json();
|
||||
|
||||
if (!order_id || !status) {
|
||||
return NextResponse.json({ error: 'order_id and status are required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const validStatuses = ['pending', 'preparing', 'ready', 'delivered', 'cancelled'];
|
||||
if (!validStatuses.includes(status)) {
|
||||
return NextResponse.json({ error: 'Invalid status' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
'UPDATE orders SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
|
||||
[status, order_id]
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return NextResponse.json({ error: 'Order not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, order: rows[0] });
|
||||
} catch (error) {
|
||||
console.error('Update order error:', error);
|
||||
return NextResponse.json({ error: 'Failed to update order' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user