diff --git a/src/components/PlansSection.tsx b/src/components/PlansSection.tsx index 3bb2b7e..cdf1278 100644 --- a/src/components/PlansSection.tsx +++ b/src/components/PlansSection.tsx @@ -1,65 +1,50 @@ -import { useState, useMemo } from "react"; -import { Loader2, CreditCard, FileText } from "lucide-react"; -import { - storefrontApiRequest, - CART_CREATE_MUTATION, - formatCheckoutUrl, -} from "@/lib/shopify"; +import { useState, useMemo, useEffect } from "react"; +import { Loader2, CheckCircle2 } from "lucide-react"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; +import { z } from "zod"; const PRICE_PER_KG = 325; const KG_PER_EMPLOYEE = 0.3; -const availablePlans = [ - { kg: 3, handle: "liten-kakabonnemang-3-kg-vecka", name: "Liten" }, - { kg: 5, handle: "mellan-kakabonnemang-5-kg-vecka", name: "Mellan" }, - { kg: 10, handle: "stor-kakabonnemang-10-kg-vecka", name: "Stor" }, -]; - -const pickPlan = (kg: number) => { - const match = availablePlans.find((p) => p.kg >= kg); - return match || availablePlans[availablePlans.length - 1]; +const isStockholmPostalCode = (postalCode: string) => { + const cleaned = postalCode.replace(/\s/g, ""); + if (!/^\d{5}$/.test(cleaned)) return false; + const num = parseInt(cleaned, 10); + return num >= 10000 && num <= 19999; }; -const PRODUCT_BY_HANDLE_QUERY = ` - query ProductByHandle($handle: String!) { - productByHandle(handle: $handle) { - id - title - handle - variants(first: 1) { - edges { - node { - id - availableForSale - } - } - } - sellingPlanGroups(first: 5) { - edges { - node { - name - sellingPlans(first: 10) { - edges { - node { - id - name - } - } - } - } - } - } - } - } -`; - -type PaymentMethod = "card" | "invoice"; +const formSchema = z.object({ + företag: z.string().trim().min(1, "Fyll i företagsnamn").max(100), + epost: z.string().trim().email("Ange en giltig e-postadress").max(255), + telefon: z.string().trim().max(30).optional(), + adress: z.string().trim().max(200).optional(), + postnummer: z + .string() + .trim() + .min(1, "Fyll i postnummer") + .refine(isStockholmPostalCode, "Vi levererar endast inom Storstockholm (100 00 – 199 99)"), + stad: z.string().trim().max(100).optional(), + kommentarer: z.string().trim().max(1000).optional(), +}); const PlansSection = () => { + const { user } = useAuth(); const [employees, setEmployees] = useState(10); - const [loadingMethod, setLoadingMethod] = useState(null); - const [showPaymentOptions, setShowPaymentOptions] = useState(false); + const [showForm, setShowForm] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [errors, setErrors] = useState>({}); + const [form, setForm] = useState({ + företag: "", + epost: "", + telefon: "", + adress: "", + postnummer: "", + stad: "", + kommentarer: "", + }); const recommendedKg = useMemo( () => Math.max(1, Math.round(employees * KG_PER_EMPLOYEE)), @@ -67,83 +52,67 @@ const PlansSection = () => { ); const weeklyPrice = recommendedKg * PRICE_PER_KG; - const handleStart = async (method: PaymentMethod) => { - const plan = pickPlan(recommendedKg); - setLoadingMethod(method); + // Prefyll från profil om inloggad + useEffect(() => { + if (!user || !showForm) return; + (async () => { + const { data } = await supabase + .from("profiles") + .select("company_name, phone, street_address, postal_code, city") + .eq("user_id", user.id) + .maybeSingle(); + setForm((prev) => ({ + ...prev, + företag: prev.företag || data?.company_name || "", + epost: prev.epost || user.email || "", + telefon: prev.telefon || data?.phone || "", + adress: prev.adress || data?.street_address || "", + postnummer: prev.postnummer || data?.postal_code || "", + stad: prev.stad || data?.city || "", + })); + })(); + }, [user, showForm]); - try { - const productData = await storefrontApiRequest(PRODUCT_BY_HANDLE_QUERY, { - handle: plan.handle, - }); - - const product = productData?.data?.productByHandle; - if (!product) { - toast.error(`Kunde inte hitta produkten "${plan.handle}" i Shopify.`); - return; - } - - const variant = product.variants?.edges?.[0]?.node; - if (!variant?.id) { - toast.error("Ingen variant tillgänglig på produkten."); - return; - } - - const sellingPlanId = - product.sellingPlanGroups?.edges?.[0]?.node?.sellingPlans?.edges?.[0]?.node?.id; - - if (!sellingPlanId) { - toast.error( - "Ingen Appstle-abonnemangsplan hittades. Aktivera Storefront-behörigheten 'unauthenticated_read_selling_plans' och koppla en Appstle-plan till produkten.", - { duration: 10000 } - ); - return; - } - - const data = await storefrontApiRequest(CART_CREATE_MUTATION, { - input: { - lines: [ - { - quantity: 1, - merchandiseId: variant.id, - sellingPlanId, - }, - ], - attributes: [ - { key: "Antal anställda", value: String(employees) }, - { key: "Rekommenderad mängd", value: `${recommendedKg} kg/vecka` }, - { key: "Veckopris (exkl. moms)", value: `${weeklyPrice} kr` }, - { - key: "Önskad betalningsmetod", - value: method === "card" ? "Automatiskt kort varje månad" : "Månadsfaktura", - }, - ], - }, - }); - - const errors = data?.data?.cartCreate?.userErrors || []; - if (errors.length > 0) { - console.error("Cart creation failed:", errors); - toast.error("Kunde inte skapa checkout: " + errors[0].message); - return; - } - - const checkoutUrl = data?.data?.cartCreate?.cart?.checkoutUrl; - if (!checkoutUrl) { - toast.error("Ingen checkout-länk mottagen"); - return; - } - - window.open(formatCheckoutUrl(checkoutUrl), "_blank"); - } catch (e) { - console.error(e); - toast.error("Något gick fel vid checkout"); - } finally { - setLoadingMethod(null); - } + const handleChange = ( + e: React.ChangeEvent + ) => { + const { name, value } = e.target; + setForm((p) => ({ ...p, [name]: value })); + if (errors[name]) setErrors((p) => ({ ...p, [name]: "" })); }; + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const result = formSchema.safeParse(form); + if (!result.success) { + const fe: Record = {}; + result.error.errors.forEach((err) => { + if (err.path[0]) fe[err.path[0] as string] = err.message; + }); + setErrors(fe); + return; + } - const isBusy = loadingMethod !== null; + setSubmitting(true); + try { + const { data, error } = await supabase.functions.invoke("submit-order", { + body: { + ...result.data, + employees, + kg_per_vecka: recommendedKg, + pris_per_vecka: weeklyPrice, + }, + }); + if (error) throw error; + if ((data as any)?.error) throw new Error((data as any).error); + setSubmitted(true); + } catch (err) { + console.error(err); + toast.error("Kunde inte skicka beställningen. Försök igen."); + } finally { + setSubmitting(false); + } + }; return (
@@ -161,114 +130,220 @@ const PlansSection = () => { {/* Calculator card */}
- {/* Employee count display */} -
-
- {employees} + {submitted ? ( +
+ +

+ Tack för din beställning! +

+

+ Vi har tagit emot din förfrågan för{" "} + + {recommendedKg} kg kakor per vecka + {" "} + och kontaktar dig inom kort för att bekräfta leverans. +

-
- {employees === 1 ? "anställd" : "anställda"} -
-
- - {/* Slider */} -
- setEmployees(parseInt(e.target.value, 10))} - className="calculator-slider w-full" - aria-label="Antal anställda" - /> -
- 1 - 100 -
-
- - {/* Result */} -
-
-
- Rekommenderad mängd -
-
- {recommendedKg} kg -
-
per vecka
-
- -
-
- Pris per vecka -
-
- {weeklyPrice.toLocaleString("sv-SE")} kr -
-
exklusive moms
-
- - {!showPaymentOptions ? ( - - ) : ( -
-

- Välj betalningsmetod -

-
- - + ) : !showForm ? ( + <> + {/* Employee count */} +
+
+ {employees}
+
+ {employees === 1 ? "anställd" : "anställda"} +
+
+ + {/* Slider */} +
+ setEmployees(parseInt(e.target.value, 10))} + className="calculator-slider w-full" + aria-label="Antal anställda" + /> +
+ 1 + 100 +
+
+ + {/* Result */} +
+
+
+ Rekommenderad mängd +
+
+ {recommendedKg} kg +
+
per vecka
+
+ +
+
+ Pris per vecka +
+
+ {weeklyPrice.toLocaleString("sv-SE")} kr +
+
exklusive moms
+
+ +
+ + ) : ( +
+
+
+ Din beställning +
+
+ {recommendedKg} kg/vecka —{" "} + + {weeklyPrice.toLocaleString("sv-SE")} kr + +
+
+ exklusive moms · {employees} {employees === 1 ? "anställd" : "anställda"} +
+
+ +
+ + + {errors.företag && ( +

{errors.företag}

+ )} +
+ +
+
+ + + {errors.epost && ( +

{errors.epost}

+ )} +
+
+ + +
+
+ +
+ + +
+ +
+
+ + + {errors.postnummer && ( +

{errors.postnummer}

+ )} +
+
+ + +
+
+ +
+ +