Fixade stad-autofyll i checkout

X-Lovable-Edit-ID: edt-695a1837-e5d9-4420-92e0-742460dc36e1
Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-07-12 13:54:07 +00:00
+89 -47
View File
@@ -6,8 +6,6 @@ import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { StripeEmbeddedCheckout } from "@/components/StripeEmbeddedCheckout"; import { StripeEmbeddedCheckout } from "@/components/StripeEmbeddedCheckout";
import { getStripeEnvironment, hasPaymentsConfigured } from "@/lib/stripe"; import { getStripeEnvironment, hasPaymentsConfigured } from "@/lib/stripe";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const STORSTOCKHOLM_CITIES = [ const STORSTOCKHOLM_CITIES = [
@@ -112,6 +110,14 @@ const PlansSection = () => {
kommentarer: "", kommentarer: "",
}); });
const filteredCities = useMemo(() => {
const query = form.stad.trim().toLocaleLowerCase("sv-SE");
if (!query) return STORSTOCKHOLM_CITIES;
return STORSTOCKHOLM_CITIES.filter((city) =>
city.toLocaleLowerCase("sv-SE").includes(query)
);
}, [form.stad]);
const recommendedKg = useMemo( const recommendedKg = useMemo(
() => Math.max(1, Math.round(employees * KG_PER_EMPLOYEE)), () => Math.max(1, Math.round(employees * KG_PER_EMPLOYEE)),
[employees] [employees]
@@ -127,18 +133,30 @@ const PlansSection = () => {
.select("company_name, phone, street_address, postal_code, city") .select("company_name, phone, street_address, postal_code, city")
.eq("user_id", user.id) .eq("user_id", user.id)
.maybeSingle(); .maybeSingle();
setForm((prev) => ({ setForm((prev) => {
...prev, const postalCode = prev.postnummer || data?.postal_code || "";
företag: prev.företag || data?.company_name || "", const suggestedCity = cityFromPostalCode(postalCode);
epost: prev.epost || user.email || "",
telefon: prev.telefon || data?.phone || "", return {
adress: prev.adress || data?.street_address || "", ...prev,
postnummer: prev.postnummer || data?.postal_code || "", företag: prev.företag || data?.company_name || "",
stad: prev.stad || data?.city || "", epost: prev.epost || user.email || "",
})); telefon: prev.telefon || data?.phone || "",
adress: prev.adress || data?.street_address || "",
postnummer: postalCode,
stad: suggestedCity || prev.stad || data?.city || "",
};
});
})(); })();
}, [user, showForm]); }, [user, showForm]);
useEffect(() => {
const suggestedCity = cityFromPostalCode(form.postnummer);
if (!suggestedCity || form.stad === suggestedCity) return;
setForm((prev) => ({ ...prev, stad: suggestedCity }));
setErrors((prev) => ({ ...prev, stad: "", postnummer: "" }));
}, [form.postnummer, form.stad]);
const handleChange = ( const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => { ) => {
@@ -476,47 +494,71 @@ const PlansSection = () => {
<label className="block mb-1.5 text-sm font-semibold"> <label className="block mb-1.5 text-sm font-semibold">
Stad <span className="text-primary">*</span> Stad <span className="text-primary">*</span>
</label> </label>
<Popover open={cityOpen} onOpenChange={setCityOpen}> <div className="relative">
<PopoverTrigger asChild> <div className="relative">
<button <input
type="button" name="stad"
role="combobox" role="combobox"
aria-expanded={cityOpen} aria-expanded={cityOpen}
aria-controls="city-options"
aria-autocomplete="list"
aria-invalid={!!errors.stad} aria-invalid={!!errors.stad}
className={cn( placeholder="Sök eller välj stad..."
"w-full px-4 py-3 rounded-sm bg-background border border-border focus:outline-none focus:ring-2 focus:ring-primary/30 flex items-center justify-between text-left", value={form.stad}
!form.stad && "text-muted-foreground" onFocus={() => setCityOpen(true)}
)} onBlur={() => window.setTimeout(() => setCityOpen(false), 120)}
onChange={(e) => {
handleChange(e);
setCityOpen(true);
}}
className="w-full px-4 py-3 pr-10 rounded-sm bg-background border border-border focus:outline-none focus:ring-2 focus:ring-primary/30"
/>
<button
type="button"
aria-label="Visa städer"
onMouseDown={(e) => e.preventDefault()}
onClick={() => setCityOpen((open) => !open)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
> >
{form.stad || "Välj stad..."} <ChevronsUpDown className="h-4 w-4" />
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</button> </button>
</PopoverTrigger> </div>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start"> {cityOpen && (
<Command> <div
<CommandInput placeholder="Sök stad..." /> id="city-options"
<CommandList> role="listbox"
<CommandEmpty>Ingen stad hittades.</CommandEmpty> className="absolute z-50 mt-1 max-h-56 w-full overflow-auto rounded-sm border border-border bg-popover shadow-md"
<CommandGroup> >
{STORSTOCKHOLM_CITIES.map((city) => ( {filteredCities.length > 0 ? (
<CommandItem filteredCities.map((city) => (
key={city} <button
value={city} key={city}
onSelect={(v) => { type="button"
setForm((f) => ({ ...f, stad: v })); role="option"
setErrors((e) => ({ ...e, stad: "" })); aria-selected={form.stad === city}
setCityOpen(false); onMouseDown={(e) => e.preventDefault()}
}} onClick={() => {
> setForm((f) => ({ ...f, stad: city }));
<Check className={cn("mr-2 h-4 w-4", form.stad === city ? "opacity-100" : "opacity-0")} /> setErrors((e) => ({ ...e, stad: "" }));
{city} setCityOpen(false);
</CommandItem> }}
))} className={cn(
</CommandGroup> "flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground",
</CommandList> form.stad === city && "bg-accent text-accent-foreground"
</Command> )}
</PopoverContent> >
</Popover> <Check className={cn("h-4 w-4", form.stad === city ? "opacity-100" : "opacity-0")} />
<span>{city}</span>
</button>
))
) : (
<div className="px-3 py-2 text-sm text-muted-foreground">
Ingen stad hittades.
</div>
)}
</div>
)}
</div>
{errors.stad && ( {errors.stad && (
<p className="text-destructive text-xs mt-1">{errors.stad}</p> <p className="text-destructive text-xs mt-1">{errors.stad}</p>
)} )}