diff --git a/apps/api/src/routes/restaurants.ts b/apps/api/src/routes/restaurants.ts index 3e63395..762f822 100644 --- a/apps/api/src/routes/restaurants.ts +++ b/apps/api/src/routes/restaurants.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; import { generateUniqueRestaurantSlug } from "../lib/slug.js"; import { supabase } from "../lib/supabase.js"; +import { deleteCustomHostname, getCustomHostnameByHostname } from "../lib/cloudflare.js"; const createRestaurantSchema = z.object({ name: z.string().min(1).max(120), @@ -206,4 +207,59 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => { return reply.send({ success: true, themeKey }); }); + + app.delete("/restaurants/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, id); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + // Delete custom hostnames from Cloudflare if any custom domains exist + const { data: domains } = await supabase.from("domains").select("id, hostname").eq("restaurant_id", id); + if (domains && domains.length > 0) { + for (const dom of domains) { + try { + const cfHostname = await getCustomHostnameByHostname(dom.hostname); + if (cfHostname?.id) { + await deleteCustomHostname(cfHostname.id); + } + } catch (err) { + req.log.warn({ err, hostname: dom.hostname }, "Cloudflare hostname delete failed during restaurant deletion"); + } + } + } + + // Delete associated database records + await supabase.from("domains").delete().eq("restaurant_id", id); + await supabase.from("restaurant_themes").delete().eq("restaurant_id", id); + await supabase.from("restaurant_members").delete().eq("restaurant_id", id); + + // Delete locations, menus, categories, items + const { data: locations } = await supabase.from("locations").select("id").eq("restaurant_id", id); + if (locations && locations.length > 0) { + const locIds = locations.map((l) => l.id); + const { data: menus } = await supabase.from("menus").select("id").in("location_id", locIds); + if (menus && menus.length > 0) { + const menuIds = menus.map((m) => m.id); + const { data: categories } = await supabase.from("menu_categories").select("id").in("menu_id", menuIds); + if (categories && categories.length > 0) { + const catIds = categories.map((c) => c.id); + await supabase.from("menu_items").delete().in("category_id", catIds); + await supabase.from("menu_categories").delete().in("menu_id", menuIds); + } + await supabase.from("menus").delete().in("location_id", locIds); + } + await supabase.from("locations").delete().eq("restaurant_id", id); + } + + const { error } = await supabase.from("restaurants").delete().eq("id", id); + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to delete restaurant" }); + } + + return reply.send({ success: true, message: "Restoran başarıyla silindi." }); + }); }; diff --git a/apps/mobile/src/app/account/index.tsx b/apps/mobile/src/app/account/index.tsx index a804cc9..3d7a00e 100644 --- a/apps/mobile/src/app/account/index.tsx +++ b/apps/mobile/src/app/account/index.tsx @@ -81,6 +81,7 @@ export default function AccountScreen() { const [logoUrl, setLogoUrl] = useState(null); const [slug, setSlug] = useState(""); const [savingRest, setSavingRest] = useState(false); + const [deletingRestaurant, setDeletingRestaurant] = useState(false); // Theme state const [selectedTheme, setSelectedTheme] = useState("elegant"); @@ -319,6 +320,39 @@ export default function AccountScreen() { ); } + async function handleDeleteRestaurant() { + if (!active) return; + Alert.alert( + "Restoranı Kalıcı Olarak Sil ⚠️", + `"${name}" restoranını ve bu restorana ait tüm menüleri, kategorileri, ürünleri ve özel alan adlarını kalıcı olarak silmek istediğinize emin misiniz?\n\nBu işlem geri alınamaz!`, + [ + { text: "Vazgeç", style: "cancel" }, + { + text: "Evet, Kalıcı Olarak Sil", + style: "destructive", + onPress: async () => { + setDeletingRestaurant(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy).catch(() => {}); + try { + await api.delete(`/restaurants/${active.restaurantId}`); + await clearActiveRestaurant(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert("Silindi 🗑️", "Restoranınız başarıyla silindi.", [ + { + text: "Tamam", + onPress: () => router.replace("/(onboarding)/restaurant"), + }, + ]); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Restoran silinemedi."); + setDeletingRestaurant(false); + } + }, + }, + ], + ); + } + if (loading) { return ( @@ -951,6 +985,51 @@ export default function AccountScreen() { + + {/* Danger Zone: Delete Restaurant */} + + + + + Tehlikeli Bölge + + + + + Restoranınızı sildiğinizde bu restorana bağlı tüm menüler, kategoriler, ürünler ve özel alan adı ayarları kalıcı olarak silinir. + + + ({ + backgroundColor: "#DC2626", + borderRadius: 12, + paddingVertical: 14, + alignItems: "center", + justifyContent: "center", + opacity: pressed || deletingRestaurant ? 0.85 : 1, + marginTop: 4, + })} + > + {deletingRestaurant ? ( + + ) : ( + + Restoranı Kalıcı Olarak Sil + + )} + + );