import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useAuth } from "@/hooks/useAuth"; import { useToast } from "@/hooks/use-toast"; import { supabase } from "@/integrations/supabase/client"; import { getStripeEnvironment, hasPaymentsConfigured } from "@/lib/stripe"; import Header from "@/components/Header"; import { Save, Package, ExternalLink } from "lucide-react"; // Validate Stockholm postal codes (100 00 - 199 99) const isStockholmPostalCode = (postalCode: string) => { if (!postalCode || postalCode.trim() === "") return true; // Allow empty const cleaned = postalCode.replace(/\s/g, ""); if (!/^\d{5}$/.test(cleaned)) return false; const num = parseInt(cleaned, 10); return num >= 10000 && num <= 19999; }; const profileSchema = z.object({ company_name: z.string().trim().max(100, { message: "Företagsnamn får max vara 100 tecken" }).optional(), street_address: z.string().trim().max(200, { message: "Gatuadress får max vara 200 tecken" }).optional(), postal_code: z .string() .trim() .max(10, { message: "Postnummer får max vara 10 tecken" }) .refine((val) => isStockholmPostalCode(val), { message: "Vi levererar endast inom Storstockholm (100 00 – 199 99)", }) .optional(), city: z.string().trim().max(100, { message: "Ort får max vara 100 tecken" }).optional(), }); interface ProfileData { company_name: string; street_address: string; postal_code: string; city: string; } const getPostalCodeError = (postalCode: string) => { if (!postalCode || postalCode.trim() === "") return null; const cleaned = postalCode.replace(/\s/g, ""); if (cleaned.length < 5) return null; // Don't show error while typing if (!/^\d{5}$/.test(cleaned)) return "Ange ett giltigt postnummer (5 siffror)"; const num = parseInt(cleaned, 10); if (num < 10000 || num > 19999) { return "Vi levererar endast inom Storstockholm (100 00 – 199 99)"; } return null; }; interface SubscriptionRow { id: string; status: string; kg_per_week: number | null; monthly_amount: number | null; current_period_end: string | null; cancel_at_period_end: boolean; collection_method: string | null; } const Account = () => { const [profile, setProfile] = useState({ company_name: "", street_address: "", postal_code: "", city: "", }); const [originalProfile, setOriginalProfile] = useState({ company_name: "", street_address: "", postal_code: "", city: "", }); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [subscription, setSubscription] = useState(null); const [openingPortal, setOpeningPortal] = useState(false); const { user, loading: authLoading, signOut } = useAuth(); const { toast } = useToast(); const navigate = useNavigate(); const hasChanges = profile.company_name !== originalProfile.company_name || profile.street_address !== originalProfile.street_address || profile.postal_code !== originalProfile.postal_code || profile.city !== originalProfile.city; useEffect(() => { if (!authLoading && !user) { navigate("/"); return; } if (user) { fetchProfile(); fetchSubscription(); } }, [user, authLoading, navigate]); const fetchSubscription = async () => { if (!user) return; try { const env = hasPaymentsConfigured() ? getStripeEnvironment() : "sandbox"; const { data } = await supabase .from("subscriptions") .select("id, status, kg_per_week, monthly_amount, current_period_end, cancel_at_period_end, collection_method") .eq("user_id", user.id) .eq("environment", env) .order("created_at", { ascending: false }) .limit(1) .maybeSingle(); setSubscription((data as SubscriptionRow | null) ?? null); } catch (e) { console.error("fetchSubscription error:", e); } }; const handleManageSubscription = async () => { if (!hasPaymentsConfigured()) { toast({ title: "Betalningar inte konfigurerade", variant: "destructive" }); return; } setOpeningPortal(true); try { const { data, error } = await supabase.functions.invoke("create-portal-session", { body: { return_url: `${window.location.origin}/account`, environment: getStripeEnvironment(), }, }); if (error) throw error; if ((data as any)?.error) throw new Error((data as any).error); const url = (data as any)?.url; if (!url) throw new Error("Ingen portal-URL mottagen"); window.open(url, "_blank", "noopener,noreferrer"); } catch (e: any) { toast({ title: "Kunde inte öppna kundportalen", description: e?.message ?? String(e), variant: "destructive", }); } finally { setOpeningPortal(false); } }; const fetchProfile = async () => { try { const { data, error } = await supabase .from("profiles") .select("company_name, street_address, postal_code, city") .eq("user_id", user!.id) .maybeSingle(); if (error) { console.error("Error fetching profile:", error); toast({ title: "Kunde inte hämta profil", description: error.message, variant: "destructive", }); } else if (data) { const profileData = { company_name: data.company_name || "", street_address: data.street_address || "", postal_code: data.postal_code || "", city: data.city || "", }; setProfile(profileData); setOriginalProfile(profileData); } } finally { setIsLoading(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const validation = profileSchema.safeParse(profile); if (!validation.success) { toast({ title: "Fel", description: validation.error.errors[0].message, variant: "destructive", }); return; } setIsSaving(true); try { const { error } = await supabase .from("profiles") .update({ company_name: profile.company_name || null, street_address: profile.street_address || null, postal_code: profile.postal_code || null, city: profile.city || null, }) .eq("user_id", user!.id); if (error) { toast({ title: "Kunde inte spara profil", description: error.message, variant: "destructive", }); } else { // Navigate to confirmation page after successful save navigate("/account/confirmation", { state: { justSaved: true } }); } } finally { setIsSaving(false); } }; const handleSignOut = async () => { await signOut(); navigate("/"); }; if (authLoading || isLoading) { return (

Laddar...

); } return (
{/* Back link - top left */}
{/* Header */}

Mitt konto

{user?.email}

{/* Profile Card */}

Mina uppgifter

setProfile({ ...profile, company_name: e.target.value }) } placeholder="Företagsnamn" />
setProfile({ ...profile, street_address: e.target.value }) } placeholder="Gatuadress" />
setProfile({ ...profile, postal_code: e.target.value }) } placeholder="Postnummer" className={getPostalCodeError(profile.postal_code) ? "border-destructive focus-visible:ring-destructive" : ""} /> {getPostalCodeError(profile.postal_code) && (

{getPostalCodeError(profile.postal_code)}

)}
setProfile({ ...profile, city: e.target.value }) } placeholder="Ort" />

Denna adress används vid beställning av abonnemang.

{hasChanges && ( )}
{/* Subscription Section */}

Mitt abonnemang

{subscription ? (
Ditt abonnemang
{subscription.kg_per_week ?? "—"} kg / vecka
{subscription.monthly_amount && (
{Math.round(subscription.monthly_amount * 1.25).toLocaleString("sv-SE")} kr/mån ink moms {" · "} {subscription.collection_method === "send_invoice" ? "Faktura" : "Kort"}
)}
{subscription.cancel_at_period_end ? "Avslutas vid periodens slut" : subscription.status === "active" ? "Aktiv" : subscription.status === "trialing" ? "Provperiod" : subscription.status === "past_due" ? "Obetald – Stripe försöker igen" : subscription.status === "canceled" ? "Avslutad" : subscription.status}
{subscription.current_period_end && (

{subscription.cancel_at_period_end || subscription.status === "canceled" ? "Sista leveransperiod till: " : "Nästa förnyelse: "} {new Date(subscription.current_period_end).toLocaleDateString("sv-SE")}

)}

Ändra kort, ladda ner fakturor eller säg upp — tillgång kvar till periodens slut.

) : (

Du har för närvarande inget aktivt abonnemang.

)}
); }; export default Account;