Skip to content

Commit

Permalink
feat: add location, fix openinghours
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Patek committed Jan 11, 2025
1 parent bc86727 commit d018f4b
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 33 deletions.
5 changes: 3 additions & 2 deletions actions/towers/tower.search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { getTowerRatingAndCount } from "@/actions/towers/towers.action";
import { Tower } from "@/typings";
import { normalizeTypesenseTowerObject } from "@/utils/normalizeTowerObject";

const Typesense = require("typesense");

Expand Down Expand Up @@ -44,13 +45,13 @@ export const searchTowers = async ({
});

if (res.found === 0) return { found: res.found, towers: [], ratings: [] };
if (!include_ratings) return { found: res.found, towers: res.hits.map((elm) => elm.document), ratings: [] };
if (!include_ratings) return { found: res.found, towers: res.hits.map((elm) => normalizeTypesenseTowerObject(elm.document)), ratings: [] };

const ratings = await Promise.all(res.hits.map((elm) => getTowerRatingAndCount(elm.document.id)));

return {
found: res.found,
towers: res.hits.map((elm) => elm.document),
towers: res.hits.map((elm) => normalizeTypesenseTowerObject(elm.document)),
ratings: ratings.map((elm, index) => ({ ...elm, id: res.hits[index].document.id })),
};
};
3 changes: 3 additions & 0 deletions components/homepage/ImageSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import "swiper/css";
//todo somehow ssr this component, use different slider idk
import { Tower } from "@/typings";
import TowerCardClient from "./TowerCard";
import useLocation from "@/hooks/useLocation";

export default function ImageSlider({ towers, ratings }: { towers: Tower[]; ratings: { avg: number; count: number }[] }) {
const { location } = useLocation();
return (
<Swiper
centeredSlides
Expand Down Expand Up @@ -49,6 +51,7 @@ export default function ImageSlider({ towers, ratings }: { towers: Tower[]; rati
avg={ratings[index].avg}
count={ratings[index].count}
photoUrl={item.mainPhotoUrl}
userLocation={location}
/>
</SwiperSlide>
))}
Expand Down
59 changes: 44 additions & 15 deletions components/homepage/TowerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
"use client";

import Link from "next/link";
import Image from "next/image";

import { Tower } from "@/typings";
import ThemedRating from "@/components/shared/ThemedRating";
import { formatDateYear } from "@/utils/date";
import { getOpeningHoursStateAndShortText } from "@/utils/openingHours";
import { cn } from "@/utils/cn";
import { getDistance } from "geolib";
import { formatDistance } from "@/utils/geo";

function TowerCardClient({
tower,
priority = false,
avg,
count,
photoUrl,
userLocation,
}: {
tower: Tower;
priority?: boolean;
avg: number;
count: number;
photoUrl: string;
userLocation: { latitude: number; longitude: number } | null;
}) {
const [state, openingHoursText] = getOpeningHoursStateAndShortText(tower.openingHours);
const locationDistance = userLocation ? getDistance(userLocation, tower.gps, 100) : null;
return (
<Link href={`/${tower.type || "rozhledna"}/${tower.nameID}`} scroll>
<div className="card card-compact min-[437px]:w-36 sm:w-40 md:w-44 lg:w-56 mx-auto transition-transform duration-200 cursor-pointer hover:scale-105 ">
Expand All @@ -31,8 +41,25 @@ function TowerCardClient({
unoptimized
/>
{tower.opened ? (
<div className="badge absolute bottom-2 left-2 text-white bg-transparent border-white">
{new Date(tower.opened).getFullYear()}
<span
className={cn(
"badge badge-sm lg:badge-md absolute bottom-7 lg:bottom-8 right-2 text-white font-bold bg-black bg-opacity-50 border-white",
{
"bottom-2 lg:bottom-2": openingHoursText === "",
}
)}
>
{formatDateYear({ date: tower.opened })}
</span>
) : null}
{openingHoursText !== "" ? (
<div
className={cn("badge badge-sm lg:badge-md absolute bottom-2 right-2 font-bold border-white", {
"badge-success": state === true,
"badge-error": state === false,
})}
>
{openingHoursText}
</div>
) : null}
</figure>
Expand All @@ -44,19 +71,21 @@ function TowerCardClient({
<ThemedRating size={20} value={avg} />
<div className="text-base text-gray-400 mt-0.5">{count}x</div>
</div>

<div className="mt-2 flex-row flex items-center">
<svg viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" className="w-5">
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="location-outline" fill="currentColor" transform="translate(106.666667, 42.666667)">
<path
d="M149.333333,7.10542736e-15 C231.807856,7.10542736e-15 298.666667,66.8588107 298.666667,149.333333 C298.666667,176.537017 291.413333,202.026667 278.683512,224.008666 C270.196964,238.663333 227.080238,313.32711 149.333333,448 C71.5864284,313.32711 28.4697022,238.663333 19.9831547,224.008666 C7.25333333,202.026667 2.84217094e-14,176.537017 2.84217094e-14,149.333333 C2.84217094e-14,66.8588107 66.8588107,7.10542736e-15 149.333333,7.10542736e-15 Z M149.333333,85.3333333 C113.987109,85.3333333 85.3333333,113.987109 85.3333333,149.333333 C85.3333333,184.679557 113.987109,213.333333 149.333333,213.333333 C184.679557,213.333333 213.333333,184.679557 213.333333,149.333333 C213.333333,113.987109 184.679557,85.3333333 149.333333,85.3333333 Z"
id="Combined-Shape"
></path>
<div className="flex justify-between items-center mt-2">
<div className="flex-row flex items-center">
<svg viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" className="w-5">
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="location-outline" fill="currentColor" transform="translate(106.666667, 42.666667)">
<path
d="M149.333333,7.10542736e-15 C231.807856,7.10542736e-15 298.666667,66.8588107 298.666667,149.333333 C298.666667,176.537017 291.413333,202.026667 278.683512,224.008666 C270.196964,238.663333 227.080238,313.32711 149.333333,448 C71.5864284,313.32711 28.4697022,238.663333 19.9831547,224.008666 C7.25333333,202.026667 2.84217094e-14,176.537017 2.84217094e-14,149.333333 C2.84217094e-14,66.8588107 66.8588107,7.10542736e-15 149.333333,7.10542736e-15 Z M149.333333,85.3333333 C113.987109,85.3333333 85.3333333,113.987109 85.3333333,149.333333 C85.3333333,184.679557 113.987109,213.333333 149.333333,213.333333 C184.679557,213.333333 213.333333,184.679557 213.333333,149.333333 C213.333333,113.987109 184.679557,85.3333333 149.333333,85.3333333 Z"
id="Combined-Shape"
></path>
</g>
</g>
</g>
</svg>
<div className="ml-2">{tower.county}</div>
</svg>
<div className="ml-2">{tower.county}</div>
</div>
{locationDistance !== null ? <div className="whitespace-nowrap">{formatDistance(locationDistance)}</div> : null}
</div>
</div>
</div>
Expand Down
41 changes: 26 additions & 15 deletions components/shared/TowerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import Link from "next/link";
import ThemedRating from "./ThemedRating";
import { getOpeningHoursStateAndShortText } from "@/utils/openingHours";
import { cn } from "@/utils/cn";
import { formatDateYear } from "@/utils/date";
import TowerCardLocation from "@/components/shared/TowerCardLocation";

const TowerCard = async ({ tower, priority = false }: { tower: Tower; priority?: boolean }) => {
const { avg, count } = await getTowerRatingAndCount(tower.id);
const [state, openingHoursText] = getOpeningHoursStateAndShortText(tower.openingHours);

return (
<Link href={`/${tower.type}/${tower.nameID}`}>
<div className="card card-compact w-full mx-auto transition-transform duration-200 cursor-pointer hover:scale-105 ">
Expand All @@ -24,13 +25,20 @@ const TowerCard = async ({ tower, priority = false }: { tower: Tower; priority?:
unoptimized
/>
{tower.opened ? (
<span className="badge absolute bottom-2 left-2 text-white font-bold bg-black bg-opacity-50 border-white">
{new Date(typeof tower.opened === "number" ? +tower.opened * 1000 : tower.opened).getFullYear()}
<span
className={cn(
"badge badge-sm lg:badge-md absolute bottom-7 lg:bottom-8 right-2 text-white font-bold bg-black bg-opacity-50 border-white",
{
"bottom-2 lg:bottom-2": openingHoursText === "",
}
)}
>
{formatDateYear({ date: tower.opened })}
</span>
) : null}
{openingHoursText !== "" ? (
<div
className={cn("badge absolute bottom-2 right-2 font-bold border-white", {
className={cn("badge badge-sm lg:badge-md absolute bottom-2 right-2 font-bold border-white", {
"badge-success": state === true,
"badge-error": state === false,
})}
Expand All @@ -48,18 +56,21 @@ const TowerCard = async ({ tower, priority = false }: { tower: Tower; priority?:
<div className="flex lg:gap-1 text-md text-gray-400 ml-2">{count}x</div>
</div>

<div className="mt-2 flex-row flex items-center">
<svg viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" className="w-5">
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="location-outline" fill="currentColor" transform="translate(106.666667, 42.666667)">
<path
d="M149.333333,7.10542736e-15 C231.807856,7.10542736e-15 298.666667,66.8588107 298.666667,149.333333 C298.666667,176.537017 291.413333,202.026667 278.683512,224.008666 C270.196964,238.663333 227.080238,313.32711 149.333333,448 C71.5864284,313.32711 28.4697022,238.663333 19.9831547,224.008666 C7.25333333,202.026667 2.84217094e-14,176.537017 2.84217094e-14,149.333333 C2.84217094e-14,66.8588107 66.8588107,7.10542736e-15 149.333333,7.10542736e-15 Z M149.333333,85.3333333 C113.987109,85.3333333 85.3333333,113.987109 85.3333333,149.333333 C85.3333333,184.679557 113.987109,213.333333 149.333333,213.333333 C184.679557,213.333333 213.333333,184.679557 213.333333,149.333333 C213.333333,113.987109 184.679557,85.3333333 149.333333,85.3333333 Z"
id="Combined-Shape"
></path>
<div className="flex justify-between items-center mt-2">
<div className="flex-row flex items-center">
<svg viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" className="w-5">
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="location-outline" fill="currentColor" transform="translate(106.666667, 42.666667)">
<path
d="M149.333333,7.10542736e-15 C231.807856,7.10542736e-15 298.666667,66.8588107 298.666667,149.333333 C298.666667,176.537017 291.413333,202.026667 278.683512,224.008666 C270.196964,238.663333 227.080238,313.32711 149.333333,448 C71.5864284,313.32711 28.4697022,238.663333 19.9831547,224.008666 C7.25333333,202.026667 2.84217094e-14,176.537017 2.84217094e-14,149.333333 C2.84217094e-14,66.8588107 66.8588107,7.10542736e-15 149.333333,7.10542736e-15 Z M149.333333,85.3333333 C113.987109,85.3333333 85.3333333,113.987109 85.3333333,149.333333 C85.3333333,184.679557 113.987109,213.333333 149.333333,213.333333 C184.679557,213.333333 213.333333,184.679557 213.333333,149.333333 C213.333333,113.987109 184.679557,85.3333333 149.333333,85.3333333 Z"
id="Combined-Shape"
></path>
</g>
</g>
</g>
</svg>
<div className="ml-2">{tower.county}</div>
</svg>
<div className="ml-2">{tower.county}</div>
</div>
<TowerCardLocation tower={tower} />
</div>
</div>
</div>
Expand Down
17 changes: 17 additions & 0 deletions components/shared/TowerCardLocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import useLocation from "@/hooks/useLocation";
import { Tower } from "@/typings";
import { formatDistance } from "@/utils/geo";
import { getDistance } from "geolib";

const TowerCardLocation = ({ tower }: { tower: Tower }) => {
const { location } = useLocation();
if (!location) {
return null;
}
const distance = getDistance(location, tower.gps, 100);
return <div>{formatDistance(distance)}</div>;
};

export default TowerCardLocation;
32 changes: 32 additions & 0 deletions hooks/useLocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// hooks/useLocation.js
import { useState, useEffect } from "react";

const useLocation = () => {
const [location, setLocation] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
if (!navigator.geolocation) {
setError("Geolocation is not supported by your browser.");
return;
}

const success = (position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
setError(null);
};

const failure = (err) => {
setError(err.message);
};

navigator.geolocation.getCurrentPosition(success, failure);
}, []);

return { location, error };
};

export default useLocation;
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"clsx": "^2.1.1",
"firebase": "^11.1.0",
"firebase-admin": "^13.0.2",
"geolib": "^3.3.4",
"next": "^15.1.4",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.4",
Expand Down
8 changes: 8 additions & 0 deletions utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ export const formatDate = ({ date, long = false }: { date: Date | string | numbe
year: "numeric",
});
};

export const formatDateYear = ({ date }: { date: Date | string | number }) => {
const dateObj = new Date(date);
return dateObj.toLocaleDateString("cs", {
timeZone: "Europe/Prague",
year: "numeric",
});
};
11 changes: 11 additions & 0 deletions utils/geo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const formatDistance = (distance: number) => {
if (distance < 1000) {
return `${Math.round(distance)} m`;
}

if (distance < 10000) {
return `${(distance / 1000).toFixed(1)} km`;
}

return `${Math.round(distance / 1000)} km`;
};
8 changes: 8 additions & 0 deletions utils/normalizeTowerObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ export const normalizeTowerObject = (tower: TowerFirebase): Tower => {
const gps: GPS = tower.gps.toJSON();
return { ...tower, opened: opened, modified: modified, created: created, gps: gps } as Tower;
};

export const normalizeTypesenseTowerObject = (tower: any): Tower => {
const opened = new Date(tower.opened * 1000).toISOString();
const modified = new Date(tower.modified * 1000).toISOString();
const created = new Date(tower.created * 1000).toISOString();
const gps: { latitude: number; longitude: number } = { latitude: tower.gps[0], longitude: tower.gps[1] };
return { ...tower, opened, modified, created, gps } as Tower;
};
2 changes: 1 addition & 1 deletion utils/openingHours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const getOpeningHoursStateAndShortText = (openingHours: OpeningHours): [b
return [false, `Otevírá v ${DAYS_CZECH[nextDay].slice(0, 2)} ${dayFrom}h`];
}
}
return [false, `Otevřeno od ${MONTHS_CZECH_4[monthFrom - 1].toLocaleLowerCase()}`];
return [false, `Otevřeno od ${MONTHS_CZECH_4[monthFrom].toLocaleLowerCase()}`];
case OpeningHoursType.EveryMonth:
if (days.includes(new Date().getDay())) {
if (dayFrom <= new Date().getHours() && dayTo >= new Date().getHours()) {
Expand Down

0 comments on commit d018f4b

Please sign in to comment.