From d33a38de4eaf5af275d553dfc58c09894d9d9789 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Sat, 29 Aug 2026 01:58:10 +0300 Subject: [PATCH] feat: restaurant cover photo upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit restaurants.cover_url existed in the schema and was already read by the public menu page, but nothing could ever write it — the update endpoint didn't accept it and there was no picker in the app. Adds coverUrl to PATCH /restaurants/:id and a cover photo picker in the mobile account screen, next to the existing logo picker. Co-Authored-By: Claude Sonnet 5 --- apps/api/src/routes/restaurants.ts | 4 +- apps/mobile/src/app/account/index.tsx | 74 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/restaurants.ts b/apps/api/src/routes/restaurants.ts index ae8f2f5..358aab3 100644 --- a/apps/api/src/routes/restaurants.ts +++ b/apps/api/src/routes/restaurants.ts @@ -16,6 +16,7 @@ const createRestaurantSchema = z.object({ const updateRestaurantSchema = z.object({ name: z.string().min(1).max(120).optional(), logoUrl: z.string().nullish(), + coverUrl: z.string().nullish(), phone: z.string().max(30).nullish(), address: z.string().max(500).nullish(), slug: z.string().min(2).max(100).optional(), @@ -108,7 +109,7 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => { return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); } - const { name, logoUrl, phone, address, slug } = parsed.data; + const { name, logoUrl, coverUrl, phone, address, slug } = parsed.data; let formattedSlug: string | undefined = undefined; if (slug !== undefined) { @@ -145,6 +146,7 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => { .update({ ...(name !== undefined ? { name } : {}), ...(logoUrl !== undefined ? { logo_url: logoUrl } : {}), + ...(coverUrl !== undefined ? { cover_url: coverUrl } : {}), ...(phone !== undefined ? { phone } : {}), ...(address !== undefined ? { address } : {}), ...(formattedSlug !== undefined ? { slug: formattedSlug } : {}), diff --git a/apps/mobile/src/app/account/index.tsx b/apps/mobile/src/app/account/index.tsx index 238ab92..de87408 100644 --- a/apps/mobile/src/app/account/index.tsx +++ b/apps/mobile/src/app/account/index.tsx @@ -26,6 +26,7 @@ interface RestaurantDetail { name: string; slug: string; logo_url: string | null; + cover_url: string | null; phone: string | null; address: string | null; enabled_languages?: string[]; @@ -88,6 +89,7 @@ export default function AccountScreen() { const [phone, setPhone] = useState(""); const [address, setAddress] = useState(""); const [logoUrl, setLogoUrl] = useState(null); + const [coverUrl, setCoverUrl] = useState(null); const [slug, setSlug] = useState(""); const [savingRest, setSavingRest] = useState(false); const [deletingRestaurant, setDeletingRestaurant] = useState(false); @@ -130,6 +132,7 @@ export default function AccountScreen() { setPhone(rest.phone ?? ""); setAddress(rest.address ?? ""); setLogoUrl(rest.logo_url ?? null); + setCoverUrl(rest.cover_url ?? null); setSlug(rest.slug); setEnabledLanguages(rest.enabled_languages ?? ["tr"]); @@ -183,6 +186,29 @@ export default function AccountScreen() { } } + async function handlePickCover() { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Kapak fotoğrafı seçebilmek için fotoğraf galerisi izni gereklidir."); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images"], + allowsEditing: true, + aspect: [16, 9], + quality: 0.6, + base64: true, + }); + + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + const newCover = asset.base64 ? `data:image/jpeg;base64,${asset.base64}` : asset.uri; + setCoverUrl(newCover); + } + } + async function handleSaveRestaurant() { if (!active || !name.trim()) return; setSavingRest(true); @@ -193,6 +219,7 @@ export default function AccountScreen() { phone: phone.trim() || null, address: address.trim() || null, logoUrl: logoUrl || null, + coverUrl: coverUrl || null, slug: slug.trim() || undefined, }); @@ -1052,6 +1079,53 @@ export default function AccountScreen() { + {/* Cover Photo Picker */} + + + Kapak Fotoğrafı (Menü Üst Görseli) + + + {coverUrl ? ( + + ) : ( + + + + Kapak Fotoğrafı Ekle + + + )} + + + 📷 {coverUrl ? "Değiştir" : "Fotoğraf Seç"} + + + + + {/* Restaurant Name */}