feat: live updates on shared meal plans
Two separate components (the owner's own week view and collaborators' shared view) hit two different API surfaces for the same underlying entries, so neither ever saw the other's edits without a manual reload. Both now poll a new lean GET on their respective entries routes every 4s, merged with the same dirty-until-ref guard used for shopping lists so a poll can't stomp an in-flight local edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,41 @@ async function getOrCreatePlan(userId: string, weekStart: string) {
|
||||
return { id, userId, weekStart };
|
||||
}
|
||||
|
||||
// Polled by MealPlanner (components/meal-plan/meal-planner.tsx) so entries
|
||||
// added/removed by a collaborator via the shared view show up here too — a
|
||||
// lean shape (no photos join) unlike the full plan GET at
|
||||
// meal-plans/[weekStart]/route.ts, since this runs every few seconds.
|
||||
export async function GET(_req: NextRequest, { params }: Params) {
|
||||
const { session, response } = await requireSession();
|
||||
if (response) return response;
|
||||
const { weekStart } = await params;
|
||||
|
||||
const plan = await db.query.mealPlans.findFirst({
|
||||
where: and(eq(mealPlans.userId, session!.user.id), eq(mealPlans.weekStart, weekStart)),
|
||||
});
|
||||
if (!plan) return NextResponse.json({ entries: [] });
|
||||
|
||||
const entries = await db.query.mealPlanEntries.findMany({
|
||||
where: eq(mealPlanEntries.mealPlanId, plan.id),
|
||||
with: {
|
||||
recipe: { columns: { id: true, title: true } },
|
||||
batchDish: { columns: { id: true, name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
entries: entries.map((e) => ({
|
||||
id: e.id,
|
||||
day: e.day,
|
||||
mealType: e.mealType,
|
||||
servings: e.servings,
|
||||
recipe: e.recipe,
|
||||
batchDish: e.batchDish,
|
||||
note: e.note,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest, { params }: Params) {
|
||||
const { session, response } = await requireSession();
|
||||
if (response) return response;
|
||||
|
||||
@@ -7,6 +7,33 @@ import { dispatchWebhook } from "@/lib/webhooks";
|
||||
|
||||
type Params = { params: Promise<{ mealPlanId: string }> };
|
||||
|
||||
// Polled by SharedMealPlanView so every collaborator sees each other's
|
||||
// changes, and the plan owner's own tab (a different component/URL, per
|
||||
// meal-plans/[weekStart]/entries) picks up edits made from here too.
|
||||
export async function GET(_req: NextRequest, { params }: Params) {
|
||||
const { session, response } = await requireSession();
|
||||
if (response) return response;
|
||||
const { mealPlanId } = await params;
|
||||
|
||||
const access = await getMealPlanAccessById(mealPlanId, session!.user.id);
|
||||
if (!access) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
|
||||
const entries = await db.query.mealPlanEntries.findMany({
|
||||
where: eq(mealPlanEntries.mealPlanId, mealPlanId),
|
||||
with: { recipe: { columns: { id: true, title: true } } },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
entries: entries.map((e) => ({
|
||||
id: e.id,
|
||||
day: e.day,
|
||||
mealType: e.mealType,
|
||||
servings: e.servings,
|
||||
recipe: e.recipe,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const Schema = z.object({
|
||||
day: z.enum(["mon", "tue", "wed", "thu", "fri", "sat", "sun"]),
|
||||
mealType: z.enum(["breakfast", "lunch", "dinner", "snack"]),
|
||||
|
||||
Reference in New Issue
Block a user