diff --git a/package-lock.json b/package-lock.json index 2f6d43f..89e9d98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,16 +15,16 @@ "date-fns": "^4.1.0", "framer-motion": "^12.23.12", "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", + "leaflet-defaulticon-compatibility": "^0.1.2", "next": "15.4.5", "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", - "react-leaflet": "^5.0.0", - "react-leaflet-markercluster": "^5.0.0-rc.0" + "react-leaflet": "^5.0.0" }, "devDependencies": { + "@types/leaflet": "^1.9.20", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1137,6 +1137,23 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.20", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz", + "integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.19.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", @@ -2375,14 +2392,11 @@ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "license": "BSD-2-Clause" }, - "node_modules/leaflet.markercluster": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", - "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", - "license": "MIT", - "peerDependencies": { - "leaflet": "^1.3.1" - } + "node_modules/leaflet-defaulticon-compatibility": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.2.tgz", + "integrity": "sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q==", + "license": "BSD-2-Clause" }, "node_modules/lines-and-columns": { "version": "1.2.4", @@ -2641,24 +2655,6 @@ "react-dom": "^19.0.0" } }, - "node_modules/react-leaflet-markercluster": { - "version": "5.0.0-rc.0", - "resolved": "https://registry.npmjs.org/react-leaflet-markercluster/-/react-leaflet-markercluster-5.0.0-rc.0.tgz", - "integrity": "sha512-jWa4bPD5LfLV3Lid1RWgl+yKUuQtnqeYtJzzLb/fiRjvX+rtwzY8pMoUFuygqyxNrWxMTQlWKBHxkpI7Sxvu4Q==", - "license": "MIT", - "dependencies": { - "@react-leaflet/core": "^3.0.0", - "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", - "react-leaflet": "^5.0.0" - }, - "peerDependencies": { - "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", - "react": "^19.0.0", - "react-leaflet": "^5.0.0" - } - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", diff --git a/package.json b/package.json index 2faa4fe..22f23da 100644 --- a/package.json +++ b/package.json @@ -16,16 +16,16 @@ "date-fns": "^4.1.0", "framer-motion": "^12.23.12", "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", + "leaflet-defaulticon-compatibility": "^0.1.2", "next": "15.4.5", "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", - "react-leaflet": "^5.0.0", - "react-leaflet-markercluster": "^5.0.0-rc.0" + "react-leaflet": "^5.0.0" }, "devDependencies": { + "@types/leaflet": "^1.9.20", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/public/icons/drustva.svg b/public/icons/drustva.svg new file mode 100755 index 0000000..e48efbd --- /dev/null +++ b/public/icons/drustva.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/frizerji.svg b/public/icons/frizerji.svg new file mode 100755 index 0000000..3672a66 --- /dev/null +++ b/public/icons/frizerji.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/icons/ostalo.svg b/public/icons/ostalo.svg new file mode 100755 index 0000000..080e5b4 --- /dev/null +++ b/public/icons/ostalo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/rejci-zivali.svg b/public/icons/rejci-zivali.svg new file mode 100755 index 0000000..db9d581 --- /dev/null +++ b/public/icons/rejci-zivali.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/trgovine.svg b/public/icons/trgovine.svg new file mode 100755 index 0000000..b30f5cf --- /dev/null +++ b/public/icons/trgovine.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/varstvo-in-sprehajanje.svg b/public/icons/varstvo-in-sprehajanje.svg new file mode 100755 index 0000000..d056471 --- /dev/null +++ b/public/icons/varstvo-in-sprehajanje.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/veterinarji.svg b/public/icons/veterinarji.svg new file mode 100755 index 0000000..e76c283 --- /dev/null +++ b/public/icons/veterinarji.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/vzgoja.svg b/public/icons/vzgoja.svg new file mode 100755 index 0000000..4eb428c --- /dev/null +++ b/public/icons/vzgoja.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/icons/zavetisca.svg b/public/icons/zavetisca.svg new file mode 100755 index 0000000..9a82f0f --- /dev/null +++ b/public/icons/zavetisca.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 941581c..e69de29 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,46 +0,0 @@ -@import "leaflet/dist/leaflet.css"; -@import "leaflet.markercluster/dist/MarkerCluster.css"; -@import "leaflet.markercluster/dist/MarkerCluster.Default.css"; - -/*:root {*/ -/* --background: #ffffff;*/ -/* --foreground: #171717;*/ -/*}*/ - -/*@media (prefers-color-scheme: dark) {*/ -/* :root {*/ -/* --background: #0a0a0a;*/ -/* --foreground: #ededed;*/ -/* }*/ -/*}*/ - -/*html,*/ -/*body {*/ -/* max-width: 100vw;*/ -/* overflow-x: hidden;*/ -/*}*/ - -/*body {*/ -/* color: var(--foreground);*/ -/* background: var(--background);*/ -/* font-family: Arial, Helvetica, sans-serif;*/ -/* -webkit-font-smoothing: antialiased;*/ -/* -moz-osx-font-smoothing: grayscale;*/ -/*}*/ - -/** {*/ -/* box-sizing: border-box;*/ -/* padding: 0;*/ -/* margin: 0;*/ -/*}*/ - -/*a {*/ -/* color: inherit;*/ -/* text-decoration: none;*/ -/*}*/ - -/*@media (prefers-color-scheme: dark) {*/ -/* html {*/ -/* color-scheme: dark;*/ -/* }*/ -/*}*/ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6a23bb1..e6b3fe2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,8 @@ import type {Metadata} from "next"; import {Geist, Geist_Mono} from "next/font/google"; +import 'leaflet/dist/leaflet.css'; import "./globals.css"; -import {Provider} from "@/components/ui/provider" +import {Provider} from "@/components/ui/provider"; const geistSans = Geist({ variable: "--font-geist-sans", diff --git a/src/app/page.tsx b/src/app/page.tsx index d0a0578..fa1e054 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,23 +1,14 @@ -import Image from "next/image"; + import styles from "./page.module.css"; -import { Container, Heading } from "@chakra-ui/react" import Header from "@/components/Header"; import HeroBanner from "@/components/HeroBanner"; -import CompaniesExplorer from "@/components/Map/CompaniesExplorer.tsx"; -import type { Company } from "@/components/Map/CompanyMap.tsx"; +import ListingsMap from "@/components/Map/ListingsMap"; import Articles from "@/components/Articles"; import Calendar from "@/components/Calendar"; import Sponsor from "@/components/Sponsor"; import Footer from "@/components/Footer"; -const seed: Company[] = [ - { id: "1", name: "ByteForge", industry: "Tech", lat: 46.0569, lng: 14.5058, address: "Ljubljana" }, - { id: "2", name: "GreenPlate", industry: "Food", lat: 46.2389, lng: 15.2675, address: "Celje" }, - { id: "3", name: "Medicus+", industry: "Healthcare", lat: 46.5547, lng: 15.6459, address: "Maribor" }, - { id: "4", name: "ShopNook", industry: "Retail", lat: 45.5481, lng: 13.7302, address: "Koper" }, - { id: "5", name: "Steelworks d.o.o.", industry: "Manufacturing", lat: 46.2396, lng: 14.3556, address: "Kranj" }, - { id: "6", name: "CloudLynx", industry: "Tech", lat: 46.3607, lng: 14.0888, address: "ล kofja Loka" }, -]; + export default function Home() { return ( @@ -26,8 +17,8 @@ export default function Home() {
- - + + diff --git a/src/components/Articles.js b/src/components/Articles.js index 037e354..31dcedf 100644 --- a/src/components/Articles.js +++ b/src/components/Articles.js @@ -3,7 +3,11 @@ import { Container, Button, Card, Heading, Image, Text, SimpleGrid, GridItem } f export default async function Articles() { const supabase = await createClient(); - const { data: articles } = await supabase.from("articles").select().order("published_at", {ascending: false}).limit(8); + const {data: articles} = await supabase + .from("articles") + .select() + .order("published_at", {ascending: false}) + .limit(8); return ( diff --git a/src/components/HeroBanner.js b/src/components/HeroBanner.js index 1a2cace..acbc97b 100644 --- a/src/components/HeroBanner.js +++ b/src/components/HeroBanner.js @@ -1,5 +1,6 @@ import {Box, Flex, Button, HStack, Wrap, Link, Text, Heading} from "@chakra-ui/react"; import NextLink from "next/link"; +import Image from 'next/image' import {RiArrowRightLine, RiMailLine} from "react-icons/ri" import {MENU_ITEMS} from "../lib/constants"; @@ -47,7 +48,13 @@ export default function Banner() { variant="solid" asChild > - {item.label} + + {item.label} {item.label} ))} diff --git a/src/components/Map.js b/src/components/Map.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Map/CompaniesExplorer.tsx b/src/components/Map/CompaniesExplorer.tsx deleted file mode 100644 index 07e75cc..0000000 --- a/src/components/Map/CompaniesExplorer.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; - -import { Box, Flex, Heading, HStack, Input, Stack, Text, Badge } from "@chakra-ui/react"; -import { useMemo, useState } from "react"; -import dynamic from "next/dynamic"; -import type { Company } from "./CompanyMap"; - -// dynamic import to avoid SSR issues -const CompanyMap = dynamic(() => import("./CompanyMap"), { ssr: false }); - -export type CompaniesExplorerProps = { - companies: Company[]; - industries?: Array; - initialIndustry?: Company["industry"] | "All"; - showAutoFit?: boolean; -}; - -const defaultIndustries: Array = [ - "Tech", - "Retail", - "Manufacturing", - "Food", - "Healthcare", -]; - -export default function CompaniesExplorer({ - companies, - industries = defaultIndustries, - initialIndustry = "All", - showAutoFit = true, - }: CompaniesExplorerProps) { - const [query, setQuery] = useState(""); - const [industry, setIndustry] = useState(initialIndustry); - const [selectedId, setSelectedId] = useState(null); - - const filtered = useMemo(() => { - const q = query.trim().toLowerCase(); - return companies.filter((c) => { - const matchesQ = - !q || - c.name.toLowerCase().includes(q) || - (c.address?.toLowerCase().includes(q) ?? false); - const matchesInd = industry === "All" || c.industry === industry; - return matchesQ && matchesInd; - }); - }, [companies, query, industry]); - - const selected = useMemo( - () => filtered.find((c) => c.id === selectedId), - [filtered, selectedId] - ); - - return ( - - {/* Left panel */} - - - Companies - - - - setQuery(e.target.value)} - /> - setIndustry(e.target.value)} - maxW="44" - borderWidth="1px" - borderRadius="md" - px={3} - py={2} - > - - {industries.map((i) => ( - - ))} - - - - - - {filtered.map((c) => { - const active = c.id === selectedId; - return ( - setSelectedId(c.id)} - _hover={{ borderColor: "blue.300" }} - > - - - {c.name} - {c.address && ( - - {c.address} - - )} - - {c.industry} - - - ); - })} - {filtered.length === 0 && ( - - No companies match your filters. - - )} - - - - - {/* Right panel */} - - setSelectedId(id)} - autoFit={showAutoFit} - /> - - - ); -} diff --git a/src/components/Map/CompanyMap.tsx b/src/components/Map/CompanyMap.tsx deleted file mode 100644 index 245fff5..0000000 --- a/src/components/Map/CompanyMap.tsx +++ /dev/null @@ -1,151 +0,0 @@ -"use client"; - -import { Box, Badge, Flex, Text } from "@chakra-ui/react"; -import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet"; -import L, { DivIcon, LatLngBoundsLiteral } from "leaflet"; -import "leaflet/dist/leaflet.css"; -import "leaflet.markercluster/dist/MarkerCluster.css"; -import "leaflet.markercluster/dist/MarkerCluster.Default.css"; -import MarkerClusterGroup from "react-leaflet-markercluster"; -import { useEffect, useMemo } from "react"; - -export type Company = { - id: string; - name: string; - industry: "Tech" | "Retail" | "Manufacturing" | "Food" | "Healthcare"; - lat: number; - lng: number; - address?: string; -}; - -type Props = { - companies: Company[]; - selectedId?: string | null; - onSelect?: (id: string) => void; - autoFit?: boolean; -}; - -const industryEmoji: Record = { - Tech: "๐Ÿ’ป", - Retail: "๐Ÿ›’", - Manufacturing: "๐Ÿญ", - Food: "๐Ÿฝ๏ธ", - Healthcare: "๐Ÿฅ", -}; - -const industryColor: Record = { - Tech: "#3b82f6", - Retail: "#f59e0b", - Manufacturing: "#6b7280", - Food: "#10b981", - Healthcare: "#ef4444", -}; - -function makeIcon(industry: Company["industry"], highlighted: boolean): DivIcon { - const size = highlighted ? 40 : 32; - const color = industryColor[industry]; - const emoji = industryEmoji[industry]; - const border = highlighted ? `3px solid ${color}` : `2px solid ${color}`; - return L.divIcon({ - className: "company-div-icon", - html: ` -
${emoji}
- `, - iconSize: [size, size], - iconAnchor: [size / 2, size / 2], - popupAnchor: [0, -size / 2], - }); -} - -function FlyTo({ lat, lng }: { lat: number; lng: number }) { - const map = useMap(); - useEffect(() => { - map.flyTo([lat, lng], Math.max(map.getZoom(), 13), { duration: 0.7 }); - }, [lat, lng, map]); - return null; -} - -function FitBoundsOnData({ points }: { points: { lat: number; lng: number }[] }) { - const map = useMap(); - useEffect(() => { - if (!points?.length) return; - let bounds: LatLngBoundsLiteral = [ - [points[0].lat, points[0].lng], - [points[0].lat, points[0].lng], - ]; - for (const p of points) { - bounds = L.latLngBounds(bounds).extend([p.lat, p.lng]) as any; - } - map.fitBounds(bounds, { padding: [30, 30] }); - }, [points, map]); - return null; -} - -export default function CompanyMap({ companies, selectedId, onSelect, autoFit = true }: Props) { - const selected = useMemo( - () => companies.find((c) => c.id === selectedId), - [companies, selectedId] - ); - - const center = useMemo<[number, number]>(() => { - if (!companies.length) return [46.0569, 14.5058]; // Ljubljana fallback - const lat = companies.reduce((a, c) => a + c.lat, 0) / companies.length; - const lng = companies.reduce((a, c) => a + c.lng, 0) / companies.length; - return [lat, lng]; - }, [companies]); - - return ( - - - - - - {companies.map((c) => ( - onSelect?.(c.id), - mouseover: (e) => e.target.openPopup(), - }} - > - - - {c.name} - - {c.industry} - - {c.address && {c.address}} - - - - ))} - - - {autoFit && companies.length > 0 && !selected && ( - ({ lat, lng }))} /> - )} - {selected && } - - - ); -} diff --git a/src/components/Map/ListingsMap.tsx b/src/components/Map/ListingsMap.tsx new file mode 100644 index 0000000..b1618bb --- /dev/null +++ b/src/components/Map/ListingsMap.tsx @@ -0,0 +1,32 @@ + +import { Container, Stack, Box, HStack, Button, Card, Heading, Image, Text, SimpleGrid, GridItem } from "@chakra-ui/react"; +import ServicesList from "./ServicesList"; +import MapWrapper from "./MapWrapper"; + +export default function ListingsMap() { + + return ( + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx new file mode 100644 index 0000000..f711f95 --- /dev/null +++ b/src/components/Map/Map.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { MapContainer, TileLayer, Marker, Popup, useMapEvents } from "react-leaflet"; +import { useEffect, useState, useCallback } from "react"; +import L from "leaflet"; +import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png"; +import markerIcon from "leaflet/dist/images/marker-icon.png"; +import markerShadow from "leaflet/dist/images/marker-shadow.png"; +import { createClient } from "@/utils/supabase/client"; + +const DefaultIcon = L.icon({ + iconRetinaUrl: (markerIcon2x as unknown as { src: string }).src || (markerIcon2x as unknown as string), + iconUrl: (markerIcon as unknown as { src: string }).src || (markerIcon as unknown as string), + shadowUrl: (markerShadow as unknown as { src: string }).src || (markerShadow as unknown as string), + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41], +}); + +L.Marker.prototype.options.icon = DefaultIcon; + +interface ServiceProvider { + id: string; + name: string; + address: string; + category: string; + phone?: string; + email?: string; + website?: string; + latitude?: number; + longitude?: number; +} + +interface MapBounds { + north: number; + south: number; + east: number; + west: number; +} + +function MapBoundsTracker({ + onBoundsChange, + userLocation +}: { + onBoundsChange: (bounds: MapBounds) => void; + userLocation?: { lat: number; lng: number } | null; +}) { + const map = useMapEvents({ + moveend: () => { + const bounds = map.getBounds(); + onBoundsChange({ + north: bounds.getNorth(), + south: bounds.getSouth(), + east: bounds.getEast(), + west: bounds.getWest() + }); + }, + zoomend: () => { + const bounds = map.getBounds(); + onBoundsChange({ + north: bounds.getNorth(), + south: bounds.getSouth(), + east: bounds.getEast(), + west: bounds.getWest() + }); + } + }); + + // Center map on user location when it becomes available + useEffect(() => { + if (userLocation && map) { + console.log("๐ŸŽฏ Centering map on user location:", userLocation); + map.setView([userLocation.lat, userLocation.lng], 13); + } + }, [userLocation, map]); + + return null; +} + +export default function Map(props: any) { + const [visibleProviders, setVisibleProviders] = useState([]); + const [loading, setLoading] = useState(false); + const [userLocation, setUserLocation] = useState<{ lat: number; lng: number } | null>(null); + const [mapCenter, setMapCenter] = useState<[number, number]>([46.1512, 14.9955]); // Default to Slovenia center + const [locationPermissionRequested, setLocationPermissionRequested] = useState(false); + + const fetchProvidersInBounds = useCallback(async (bounds: MapBounds) => { + console.log("๐Ÿ—บ๏ธ Fetching providers for bounds:", bounds); + setLoading(true); + try { + const supabase = createClient(); + + // Fetch all providers with coordinates, then filter by bounds + const { data: allProviders, error } = await supabase + .from("service_providers") + .select("*"); + + console.log("๐Ÿ” Supabase query result:", { data: allProviders?.length, error }); + + if (error) { + console.error("โŒ Supabase error:", error); + setVisibleProviders([]); + return; + } + + // Filter to only providers with coordinates + const providersWithCoords = allProviders?.filter(provider => + provider.latitude != null && provider.longitude != null + ) || []; + + console.log("๐Ÿ“Š Total providers with coordinates:", providersWithCoords?.length); + + // Filter by bounds on the client side for now + const serviceProviders = providersWithCoords?.filter(provider => + provider.latitude >= bounds.south && + provider.latitude <= bounds.north && + provider.longitude >= bounds.west && + provider.longitude <= bounds.east + ) || []; + + console.log("๐Ÿ“Š Providers in bounds:", serviceProviders?.length); + + setVisibleProviders(serviceProviders); + } catch (error) { + console.error("Error fetching providers:", error); + setVisibleProviders([]); + } finally { + setLoading(false); + } + }, []); + + const handleBoundsChange = useCallback((bounds: MapBounds) => { + fetchProvidersInBounds(bounds); + }, [fetchProvidersInBounds]); + + // Request user location on component mount + useEffect(() => { + if (!locationPermissionRequested && navigator.geolocation) { + setLocationPermissionRequested(true); + + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + console.log("๐Ÿ“ User location detected:", { latitude, longitude }); + const userLoc = { lat: latitude, lng: longitude }; + setUserLocation(userLoc); + setMapCenter([latitude, longitude]); + + // Fetch providers around user location (wider area) + const userBounds: MapBounds = { + north: latitude + 2.0, + south: latitude - 2.0, + east: longitude + 2.0, + west: longitude - 2.0 + }; + console.log("๐Ÿ‘ค User location bounds:", userBounds); + fetchProvidersInBounds(userBounds); + }, + (error) => { + console.warn("Geolocation error:", error.message); + // Fall back to Slovenia bounds + const sloveniaInitialBounds: MapBounds = { + north: 46.9, + south: 45.4, + east: 16.6, + west: 13.4 + }; + fetchProvidersInBounds(sloveniaInitialBounds); + }, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 300000 // 5 minutes + } + ); + } + }, [locationPermissionRequested, fetchProvidersInBounds, setUserLocation, setMapCenter, setLocationPermissionRequested]); + + return ( + + + + + {/* User location marker */} + {userLocation && ( + + +
+

+ ๐Ÿ“ Your Location +

+

+ You are here +

+
+
+
+ )} + + {!loading && visibleProviders.map((provider: ServiceProvider) => { + if (provider.latitude && provider.longitude) { + return ( + + +
+

+ {provider.name} +

+ {provider.category && ( +

+ {provider.category} +

+ )} + {provider.address && ( +

+ ๐Ÿ“ {provider.address} +

+ )} + {provider.phone && ( +

+ ๐Ÿ“ž {provider.phone} +

+ )} + {provider.website && ( +

+ ๐ŸŒ + Website + +

+ )} +
+
+
+ ); + } + return null; + })} +
+ ) +} \ No newline at end of file diff --git a/src/components/Map/MapWrapper.tsx b/src/components/Map/MapWrapper.tsx new file mode 100644 index 0000000..1f03e07 --- /dev/null +++ b/src/components/Map/MapWrapper.tsx @@ -0,0 +1,15 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { Container, Stack, Box, HStack, Button, Card, Heading, Image, Text, SimpleGrid, GridItem } from "@chakra-ui/react"; + +const Map = dynamic(() => import("./Map"), { + ssr: false, +}); + +export default function MapWrapper() { + + return ( + + ) +} \ No newline at end of file diff --git a/src/components/Map/ServicesList.tsx b/src/components/Map/ServicesList.tsx new file mode 100644 index 0000000..46d0f0a --- /dev/null +++ b/src/components/Map/ServicesList.tsx @@ -0,0 +1,125 @@ +import { Box, Heading, Text, Stack, VStack, HStack, Badge, Link } from "@chakra-ui/react"; +import { FiPhone, FiMail, FiMapPin, FiExternalLink } from "react-icons/fi"; +import { createClient } from '@/utils/supabase/server'; + +export default async function ServicesList() { + const supabase = await createClient(); + const { data:providers } = await supabase.from("service_providers").select(); + + return ( + + + + Service Providers ({providers?.length || 0}) + + + + {providers?.length === 0 ? ( + Ni vsebin + ) : ( + + + {providers?.map((provider) => ( + + + + + {provider.name} + + {provider.category && ( + + {provider.category} + + )} + + + {provider.address && ( + + + {provider.address} + + )} + + + {provider.phone && ( + + + + {provider.phone} + + + )} + + {provider.email && ( + + + + Email + + + )} + + {provider.website && ( + + + + Website + + + )} + + + + ))} + + + )} + + ) +} \ No newline at end of file diff --git a/src/lib/constants.js b/src/lib/constants.js index d34cd75..f011dd6 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -1,42 +1,42 @@ export const MENU_ITEMS = [ { label: "Veterinarji", - icon: "", + icon: "icons/veterinarji.svg", href: "/veterinarji" }, { label: "Trgovine", - icon: "", + icon: "icons/trgovine.svg", href: "/trgovine" }, { label: "Vzgoja", - icon: "", + icon: "icons/vzgoja.svg", href: "/vzgoja" }, { label: "Varstvo in sprehajanje", - icon: "", + icon: "icons/varstvo-in-sprehajanje.svg", href: "/varstvo-in-sprehajanje" },{ label: "Frizerji", - icon: "", + icon: "icons/frizerji.svg", href: "/frizerji" },{ label: "Druลกtva", - icon: "", + icon: "icons/drustva.svg", href: "/drustva" },{ label: "Rejci ลพivali", - icon: "", + icon: "icons/rejci-zivali.svg", href: "/rejci-zivali" },{ label: "Zavetiลกฤa", - icon: "", + icon: "icons/zavetisca.svg", href: "/zavetisca" },{ label: "Ostalo", - icon: "", + icon: "icons/ostalo.svg", href: "/ostalo" }, ] \ No newline at end of file