feat: anonymous read-only public link + QR code for meal plans (v0.68.0)
Mirrors shopping lists' isPublic pattern but read-only only -- no
publicEditable equivalent, since anonymous editing of someone's
weekly meal schedule isn't a real use case the way collaborative
shopping-list editing is. New PATCH /api/v1/meal-plans/{weekStart}
toggles it (lazily creates the week's plan row if missing, same
pattern as the existing POST). Public page at /m/[id] renders the
week grouped by day, linking through to public/unlisted recipes and
just showing the title otherwise.
ShareMealPlanButton gained the same public-link toggle UI as its
shopping-list sibling, plus a QR code (generated client-side via the
already-installed `qrcode` package) for the link once enabled --
handing someone a printed or screenshotted plan doesn't require
typing a URL.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,7 @@ export async function POST(req: NextRequest) {
|
||||
if (!mealPlan) {
|
||||
const planId = crypto.randomUUID();
|
||||
await db.insert(mealPlans).values({ id: planId, userId, weekStart: parsed.data.weekStart });
|
||||
mealPlan = { id: planId, userId, weekStart: parsed.data.weekStart, createdAt: new Date() };
|
||||
mealPlan = { id: planId, userId, weekStart: parsed.data.weekStart, isPublic: false, createdAt: new Date() };
|
||||
}
|
||||
|
||||
const createdEntries: Array<{ id: string; day: string; mealType: string; recipeId: string; recipeTitle: string }> = [];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { db, mealPlans, eq, and } from "@epicure/db";
|
||||
import { requireSessionOrApiKey } from "@/lib/api-auth";
|
||||
|
||||
@@ -41,3 +42,31 @@ export async function POST(req: NextRequest, { params }: Params) {
|
||||
await db.insert(mealPlans).values({ id, userId: session!.user.id, weekStart });
|
||||
return NextResponse.json({ id, weekStart }, { status: 201 });
|
||||
}
|
||||
|
||||
const PatchSchema = z.object({
|
||||
isPublic: z.boolean(),
|
||||
});
|
||||
|
||||
export async function PATCH(req: NextRequest, { params }: Params) {
|
||||
const { session, response } = await requireSessionOrApiKey(req);
|
||||
if (response) return response;
|
||||
const { weekStart } = await params;
|
||||
|
||||
const parsed = PatchSchema.safeParse(await req.json());
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Validation error", issues: parsed.error.issues }, { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await db.query.mealPlans.findFirst({
|
||||
where: and(eq(mealPlans.userId, session!.user.id), eq(mealPlans.weekStart, weekStart)),
|
||||
});
|
||||
|
||||
const id = existing?.id ?? crypto.randomUUID();
|
||||
if (!existing) {
|
||||
await db.insert(mealPlans).values({ id, userId: session!.user.id, weekStart, isPublic: parsed.data.isPublic });
|
||||
} else {
|
||||
await db.update(mealPlans).set({ isPublic: parsed.data.isPublic }).where(eq(mealPlans.id, id));
|
||||
}
|
||||
|
||||
return NextResponse.json({ id, weekStart, isPublic: parsed.data.isPublic });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user