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:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user