mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Filtering by zone
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
"@next/bundle-analyzer": "^14.0.4",
|
"@next/bundle-analyzer": "^14.0.4",
|
||||||
"@tanstack/react-query": "^5.29.0",
|
"@tanstack/react-query": "^5.29.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
||||||
|
"@turf/boolean-point-in-polygon": "^7.0.0-alpha.114",
|
||||||
"@types/node": "^20.8.4",
|
"@types/node": "^20.8.4",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import L, { LatLngLiteral } from "leaflet";
|
|
||||||
import "leaflet-defaulticon-compatibility";
|
import "leaflet-defaulticon-compatibility";
|
||||||
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
||||||
import "leaflet.smooth_marker_bouncing";
|
import "leaflet.smooth_marker_bouncing";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { countBy, groupBy } from "lodash";
|
|
||||||
|
|
||||||
import { clubsAtom } from "@/atoms";
|
import { filteredClubsByZoneAtom } from "@/atoms";
|
||||||
import { Map, MapMarker } from "@/components/Map";
|
import { Map, MapMarker } from "@/components/Map";
|
||||||
|
|
||||||
import { ClubMarker } from "./ClubMarker";
|
import { ClubMarker } from "./ClubMarker";
|
||||||
|
|
||||||
const ClubMap = () => {
|
const ClubMap = () => {
|
||||||
const clubs = useAtomValue(clubsAtom);
|
const clubs = useAtomValue(filteredClubsByZoneAtom);
|
||||||
|
|
||||||
const markers: MapMarker[] = useMemo(
|
const markers: MapMarker[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { FaExternalLinkAlt } from "react-icons/fa";
|
import { FaExternalLinkAlt } from "react-icons/fa";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
@@ -12,17 +12,15 @@ import {
|
|||||||
debouncedHoveredMapIdAtom,
|
debouncedHoveredMapIdAtom,
|
||||||
filteredClubsListAtom,
|
filteredClubsListAtom,
|
||||||
hoveredMapIdAtom,
|
hoveredMapIdAtom,
|
||||||
syncVisibleAtom,
|
|
||||||
} from "@/atoms";
|
} from "@/atoms";
|
||||||
|
import { RegionSelect } from "@/components/RegionSelect";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import { useBreakpoint } from "@/hooks/tailwind";
|
import { useBreakpoint } from "@/hooks/tailwind";
|
||||||
|
|
||||||
const ClubTable = () => {
|
const ClubTable = () => {
|
||||||
const t = useTranslations("Clubs");
|
const t = useTranslations("Clubs");
|
||||||
const at = useTranslations("App");
|
|
||||||
|
|
||||||
const filteredClubs = useAtomValue(filteredClubsListAtom);
|
const filteredClubs = useAtomValue(filteredClubsListAtom);
|
||||||
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
|
|
||||||
const hoveredMapId = useAtomValue(hoveredMapIdAtom);
|
const hoveredMapId = useAtomValue(hoveredMapIdAtom);
|
||||||
const debouncedHoveredMapId = useAtomValue(debouncedHoveredMapIdAtom);
|
const debouncedHoveredMapId = useAtomValue(debouncedHoveredMapIdAtom);
|
||||||
const setHoveredListId = useSetAtom(debouncedHoveredListIdAtom);
|
const setHoveredListId = useSetAtom(debouncedHoveredListIdAtom);
|
||||||
@@ -45,18 +43,12 @@ const ClubTable = () => {
|
|||||||
>
|
>
|
||||||
<div className="z-10 flex w-full flex-wrap items-center justify-between gap-3 p-3">
|
<div className="z-10 flex w-full flex-wrap items-center justify-between gap-3 p-3">
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
|
<RegionSelect
|
||||||
<div className="flex flex-col gap-0 text-gray-900 dark:text-white">
|
classNameOverrides={{
|
||||||
<label>
|
container: () => "flex flex-1",
|
||||||
<input
|
}}
|
||||||
type="checkbox"
|
|
||||||
className="mr-2 h-4 w-4 rounded border-gray-400 text-primary focus:ring-primary"
|
|
||||||
checked={syncVisible}
|
|
||||||
onChange={() => setSyncVisible(!syncVisible)}
|
|
||||||
/>
|
/>
|
||||||
{t("syncWithMapCheckbox")}
|
<div className="flex flex-col gap-0 text-gray-900 dark:text-white"></div>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-scroll bg-red-50">
|
<div className="overflow-x-scroll bg-red-50">
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import L from "leaflet";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useMap } from "react-leaflet";
|
import { useMap } from "react-leaflet";
|
||||||
|
|
||||||
import { filteredTournamentsByTimeControlAtom } from "@/atoms";
|
import { filteredTournamentsByTimeControlAndZoneAtom } from "@/atoms";
|
||||||
import { TimeControlColours } from "@/constants";
|
import { TimeControlColours } from "@/constants";
|
||||||
import { TimeControl } from "@/types";
|
import { TimeControl } from "@/types";
|
||||||
|
|
||||||
const Legend = () => {
|
const Legend = () => {
|
||||||
const at = useTranslations("App");
|
const at = useTranslations("App");
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const tournaments = useAtomValue(filteredTournamentsByTimeControlAtom);
|
const tournaments = useAtomValue(filteredTournamentsByTimeControlAndZoneAtom);
|
||||||
|
|
||||||
const timeControls = useMemo(
|
const timeControls = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import "leaflet.smooth_marker_bouncing";
|
|||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { countBy, groupBy } from "lodash";
|
import { countBy, groupBy } from "lodash";
|
||||||
|
|
||||||
import { filteredTournamentsByTimeControlAtom, normsOnlyAtom } from "@/atoms";
|
import {
|
||||||
|
filteredTournamentsByTimeControlAndZoneAtom,
|
||||||
|
normsOnlyAtom,
|
||||||
|
} from "@/atoms";
|
||||||
import { Map, MapMarker } from "@/components/Map";
|
import { Map, MapMarker } from "@/components/Map";
|
||||||
import { TimeControlColours } from "@/constants";
|
import { TimeControlColours } from "@/constants";
|
||||||
import { generatePieSVG } from "@/lib/pie";
|
import { generatePieSVG } from "@/lib/pie";
|
||||||
@@ -21,7 +24,7 @@ import TimeControlFilters from "./TimeControlFilters";
|
|||||||
import { TournamentMarker } from "./TournamentMarker";
|
import { TournamentMarker } from "./TournamentMarker";
|
||||||
|
|
||||||
const TournamentMap = () => {
|
const TournamentMap = () => {
|
||||||
const tournaments = useAtomValue(filteredTournamentsByTimeControlAtom);
|
const tournaments = useAtomValue(filteredTournamentsByTimeControlAndZoneAtom);
|
||||||
const normsOnly = useAtomValue(normsOnlyAtom);
|
const normsOnly = useAtomValue(normsOnlyAtom);
|
||||||
|
|
||||||
const filteredTournaments = useMemo(
|
const filteredTournaments = useMemo(
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import {
|
|||||||
filteredTournamentsListAtom,
|
filteredTournamentsListAtom,
|
||||||
hoveredMapIdAtom,
|
hoveredMapIdAtom,
|
||||||
normsOnlyAtom,
|
normsOnlyAtom,
|
||||||
syncVisibleAtom,
|
regionFilterAtom,
|
||||||
} from "@/atoms";
|
} from "@/atoms";
|
||||||
|
import { RegionSelect } from "@/components/RegionSelect";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
|
import { Select } from "@/components/form/Select";
|
||||||
import { useBreakpoint } from "@/hooks/tailwind";
|
import { useBreakpoint } from "@/hooks/tailwind";
|
||||||
import useDatePickerWidth from "@/hooks/useDatePickerWidth";
|
import useDatePickerWidth from "@/hooks/useDatePickerWidth";
|
||||||
import { DatePickerDirection } from "@/types";
|
import { DatePickerDirection } from "@/types";
|
||||||
@@ -36,7 +38,6 @@ const TournamentTable = () => {
|
|||||||
|
|
||||||
const filteredTournaments = useAtomValue(filteredTournamentsListAtom);
|
const filteredTournaments = useAtomValue(filteredTournamentsListAtom);
|
||||||
|
|
||||||
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
|
|
||||||
const [normsOnly, setNormsOnly] = useAtom(normsOnlyAtom);
|
const [normsOnly, setNormsOnly] = useAtom(normsOnlyAtom);
|
||||||
const hoveredMapId = useAtomValue(hoveredMapIdAtom);
|
const hoveredMapId = useAtomValue(hoveredMapIdAtom);
|
||||||
const debouncedHoveredMapId = useAtomValue(debouncedHoveredMapIdAtom);
|
const debouncedHoveredMapId = useAtomValue(debouncedHoveredMapIdAtom);
|
||||||
@@ -87,16 +88,6 @@ const TournamentTable = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-col gap-0 text-gray-900 dark:text-white">
|
<div className="flex flex-col gap-0 text-gray-900 dark:text-white">
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mr-2 h-4 w-4 rounded border-gray-400 text-primary focus:ring-primary"
|
|
||||||
checked={syncVisible}
|
|
||||||
onChange={() => setSyncVisible(!syncVisible)}
|
|
||||||
/>
|
|
||||||
{t("syncWithMapCheckbox")}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -111,6 +102,8 @@ const TournamentTable = () => {
|
|||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<TimeControlFilters />
|
<TimeControlFilters />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<RegionSelect />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center" ref={datePickerRef}>
|
<div className="flex justify-center" ref={datePickerRef}>
|
||||||
|
|||||||
+59
-18
@@ -1,6 +1,6 @@
|
|||||||
|
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
|
||||||
import {
|
import {
|
||||||
endOfDay,
|
endOfDay,
|
||||||
formatISO,
|
|
||||||
isAfter,
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
parse,
|
parse,
|
||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
startOfDay,
|
startOfDay,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { fr } from "date-fns/locale";
|
import { fr } from "date-fns/locale";
|
||||||
|
import { FeatureCollection } from "geojson";
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { LatLngBounds } from "leaflet";
|
import { LatLngBounds } from "leaflet";
|
||||||
import { c } from "next-safe-action/dist/index-EKyvnpX_.mjs";
|
|
||||||
|
|
||||||
import { Club, TimeControl, Tournament } from "@/types";
|
import { Club, TimeControl, Tournament } from "@/types";
|
||||||
import atomWithDebounce from "@/utils/atomWithDebounce";
|
import atomWithDebounce from "@/utils/atomWithDebounce";
|
||||||
@@ -18,9 +18,18 @@ import { normalizedContains } from "@/utils/string";
|
|||||||
|
|
||||||
setDefaultOptions({ locale: fr });
|
setDefaultOptions({ locale: fr });
|
||||||
|
|
||||||
|
type RegionFilter =
|
||||||
|
| "all"
|
||||||
|
| "map"
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
features: FeatureCollection;
|
||||||
|
};
|
||||||
|
|
||||||
export const burgerMenuIsOpenAtom = atom(false);
|
export const burgerMenuIsOpenAtom = atom(false);
|
||||||
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
||||||
export const syncVisibleAtom = atom(true);
|
export const regionFilterAtom = atom<RegionFilter>("map");
|
||||||
export const searchStringAtom = atom("");
|
export const searchStringAtom = atom("");
|
||||||
|
|
||||||
export const tournamentsAtom = atom<Tournament[]>([]);
|
export const tournamentsAtom = atom<Tournament[]>([]);
|
||||||
@@ -41,8 +50,9 @@ export const {
|
|||||||
export const { debouncedValueAtom: debouncedHoveredListIdAtom } =
|
export const { debouncedValueAtom: debouncedHoveredListIdAtom } =
|
||||||
atomWithDebounce<string | null>(null, 1000, 100);
|
atomWithDebounce<string | null>(null, 1000, 100);
|
||||||
|
|
||||||
export const filteredTournamentsByTimeControlAtom = atom((get) => {
|
export const filteredTournamentsByTimeControlAndZoneAtom = atom((get) => {
|
||||||
const tournaments = get(tournamentsAtom);
|
const tournaments = get(tournamentsAtom);
|
||||||
|
const regionFilter = get(regionFilterAtom);
|
||||||
|
|
||||||
const classic = get(classicAtom);
|
const classic = get(classicAtom);
|
||||||
const rapid = get(rapidAtom);
|
const rapid = get(rapidAtom);
|
||||||
@@ -52,30 +62,43 @@ export const filteredTournamentsByTimeControlAtom = atom((get) => {
|
|||||||
const dateRange = get(dateRangeAtom);
|
const dateRange = get(dateRangeAtom);
|
||||||
const { startDate, endDate } = dateRange[0];
|
const { startDate, endDate } = dateRange[0];
|
||||||
|
|
||||||
const filteredTournaments = tournaments.filter((tournament) => {
|
const filterByTimeControl = tournaments.filter((tournament) => {
|
||||||
const tournamentDate = startOfDay(
|
const tournamentDate = startOfDay(
|
||||||
parse(tournament.date, "dd/MM/yyyy", new Date()),
|
parse(tournament.date, "dd/MM/yyyy", new Date()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!isBefore(tournamentDate, startDate) &&
|
!isBefore(tournamentDate, startDate) &&
|
||||||
(endDate === undefined || !isAfter(tournamentDate, endDate)) &&
|
(endDate === undefined ||
|
||||||
|
(!isAfter(tournamentDate, endDate) &&
|
||||||
!tournament.pending &&
|
!tournament.pending &&
|
||||||
tournament.status === "scheduled" &&
|
tournament.status === "scheduled" &&
|
||||||
((tournament.timeControl === TimeControl.Classic && classic) ||
|
((tournament.timeControl === TimeControl.Classic && classic) ||
|
||||||
(tournament.timeControl === TimeControl.Rapid && rapid) ||
|
(tournament.timeControl === TimeControl.Rapid && rapid) ||
|
||||||
(tournament.timeControl === TimeControl.Blitz && blitz) ||
|
(tournament.timeControl === TimeControl.Blitz && blitz) ||
|
||||||
(tournament.timeControl === TimeControl.Other && other))
|
(tournament.timeControl === TimeControl.Other && other))))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return filteredTournaments;
|
if (regionFilter === "all" || regionFilter === "map")
|
||||||
|
return filterByTimeControl;
|
||||||
|
|
||||||
|
return filterByTimeControl.filter((tournament) => {
|
||||||
|
return regionFilter.features?.features?.some(
|
||||||
|
(feature) =>
|
||||||
|
feature.geometry.type === "Polygon" &&
|
||||||
|
booleanPointInPolygon(
|
||||||
|
[tournament.latLng.lng, tournament.latLng.lat],
|
||||||
|
feature.geometry,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const filteredTournamentsListAtom = atom((get) => {
|
export const filteredTournamentsListAtom = atom((get) => {
|
||||||
const tournaments = get(filteredTournamentsByTimeControlAtom);
|
const tournaments = get(filteredTournamentsByTimeControlAndZoneAtom);
|
||||||
const mapBounds = get(mapBoundsAtom);
|
const mapBounds = get(mapBoundsAtom);
|
||||||
const syncVisible = get(syncVisibleAtom);
|
const regionFilter = get(regionFilterAtom);
|
||||||
const normsOnly = get(normsOnlyAtom);
|
const normsOnly = get(normsOnlyAtom);
|
||||||
const searchString = get(searchStringAtom).trim();
|
const searchString = get(searchStringAtom).trim();
|
||||||
|
|
||||||
@@ -93,23 +116,41 @@ export const filteredTournamentsListAtom = atom((get) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we not syncing to the map, return all tournaments
|
// If we not syncing to the map, return all tournaments
|
||||||
if (mapBounds === null || !syncVisible) return filteredByNorm;
|
if (mapBounds === null || regionFilter !== "map") return filteredByNorm;
|
||||||
|
|
||||||
// Filter by those in the current map bounds
|
// Filter by the map bounds
|
||||||
return filteredByNorm.filter((tournament) =>
|
return filteredByNorm.filter((tournament) =>
|
||||||
mapBounds.contains(tournament.latLng),
|
mapBounds.contains(tournament.latLng),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const filteredClubsListAtom = atom((get) => {
|
export const filteredClubsByZoneAtom = atom((get) => {
|
||||||
const clubs = get(clubsAtom);
|
const clubs = get(clubsAtom);
|
||||||
|
const regionFilter = get(regionFilterAtom);
|
||||||
|
|
||||||
|
if (regionFilter === "all" || regionFilter === "map") return clubs;
|
||||||
|
|
||||||
|
return clubs.filter((club) => {
|
||||||
|
return regionFilter.features?.features?.some(
|
||||||
|
(feature) =>
|
||||||
|
feature.geometry.type === "Polygon" &&
|
||||||
|
booleanPointInPolygon(
|
||||||
|
[club.latLng.lng, club.latLng.lat],
|
||||||
|
feature.geometry,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const filteredClubsListAtom = atom((get) => {
|
||||||
|
const filteredByZone = get(filteredClubsByZoneAtom);
|
||||||
const mapBounds = get(mapBoundsAtom);
|
const mapBounds = get(mapBoundsAtom);
|
||||||
const syncVisible = get(syncVisibleAtom);
|
const regionFilter = get(regionFilterAtom);
|
||||||
const searchString = get(searchStringAtom).trim();
|
const searchString = get(searchStringAtom).trim();
|
||||||
|
|
||||||
// When searching, we search all the tournament, regardless of the map display
|
// When searching, we search all the tournament, regardless of the map display
|
||||||
if (searchString !== "") {
|
if (searchString !== "") {
|
||||||
return clubs.filter(
|
return filteredByZone.filter(
|
||||||
(club) =>
|
(club) =>
|
||||||
normalizedContains(club.name, searchString) ||
|
normalizedContains(club.name, searchString) ||
|
||||||
(club.address && normalizedContains(club.address, searchString)),
|
(club.address && normalizedContains(club.address, searchString)),
|
||||||
@@ -117,10 +158,10 @@ export const filteredClubsListAtom = atom((get) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we not syncing to the map, return all clubs
|
// If we not syncing to the map, return all clubs
|
||||||
if (mapBounds === null || !syncVisible) return clubs;
|
if (mapBounds === null || regionFilter !== "map") return filteredByZone;
|
||||||
|
|
||||||
// Filter by those in the current map bounds
|
// Filter by the map bounds
|
||||||
return clubs.filter((club) => mapBounds.contains(club.latLng));
|
return filteredByZone.filter((club) => mapBounds.contains(club.latLng));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Date picker atoms
|
// Date picker atoms
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
|
||||||
|
import { regionFilterAtom } from "@/atoms";
|
||||||
|
import { BaseOption, Select, SelectProps } from "@/components/form/Select";
|
||||||
|
import { useZones } from "@/hooks/useZones";
|
||||||
|
import { Zone } from "@/server/myZones";
|
||||||
|
|
||||||
|
type RegionSelectProps = Omit<SelectProps, "options" | "value" | "onChange">;
|
||||||
|
|
||||||
|
export const RegionSelect = (selectProps: RegionSelectProps) => {
|
||||||
|
const t = useTranslations("Tournaments");
|
||||||
|
const [regionFilter, setRegionFilter] = useAtom(regionFilterAtom);
|
||||||
|
const { zones } = useZones();
|
||||||
|
|
||||||
|
const zoneFilterOptions: BaseOption<string, Zone | null>[] = [
|
||||||
|
{
|
||||||
|
value: "map",
|
||||||
|
label: t("syncWithMapOption"),
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "all",
|
||||||
|
label: t("ignoreMapOption"),
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
...zones.map((zone) => ({
|
||||||
|
value: zone.id,
|
||||||
|
label: zone.name,
|
||||||
|
data: zone,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const onChange = (option: SingleValue<BaseOption<string, Zone | null>>) => {
|
||||||
|
if (!option) return;
|
||||||
|
|
||||||
|
if (option.value === "map" || option.value === "all")
|
||||||
|
setRegionFilter(option.value);
|
||||||
|
else setRegionFilter(option.data!);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
options={zoneFilterOptions}
|
||||||
|
value={zoneFilterOptions.find((o) =>
|
||||||
|
regionFilter === "all" || regionFilter === "map"
|
||||||
|
? o.value === regionFilter
|
||||||
|
: o.value === regionFilter.id,
|
||||||
|
)}
|
||||||
|
onChange={(v) =>
|
||||||
|
onChange(v as SingleValue<BaseOption<string, Zone | null>>)
|
||||||
|
}
|
||||||
|
{...selectProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
@@ -37,6 +37,18 @@ export const SignInForm = ({ callbackPath }: SignInFormProps) => {
|
|||||||
resolver: zodResolver(signInSchema),
|
resolver: zodResolver(signInSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Clear the response message when the email field changes
|
||||||
|
const subscription = form.watch((value, { name, type }) => {
|
||||||
|
if (type === "change") {
|
||||||
|
if (name === "email" && responseMessage.message === "") {
|
||||||
|
setResponseMessage({ isSuccessful: true, message: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [form, form.watch]);
|
||||||
|
|
||||||
const onSubmit = async ({ email }: SignInFormValues) => {
|
const onSubmit = async ({ email }: SignInFormValues) => {
|
||||||
try {
|
try {
|
||||||
const result = await signIn("nodemailer", {
|
const result = await signIn("nodemailer", {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import AsyncSelect from "react-select/async";
|
|||||||
import { Prettify } from "@/types";
|
import { Prettify } from "@/types";
|
||||||
|
|
||||||
import { Field, GenericFieldProps } from "./Field";
|
import { Field, GenericFieldProps } from "./Field";
|
||||||
import { BaseOption, classNames } from "./SelectField";
|
import { classNames } from "./Select";
|
||||||
|
import { BaseOption } from "./SelectField";
|
||||||
|
|
||||||
export type AsyncSelectFieldProps<
|
export type AsyncSelectFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import ReactSelect, { ClassNamesConfig, GroupBase, Props } from "react-select";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { Prettify } from "@/types";
|
||||||
|
|
||||||
|
export type BaseOption<T = string, D = unknown> = {
|
||||||
|
value: T;
|
||||||
|
label: string | JSX.Element;
|
||||||
|
disabled?: boolean;
|
||||||
|
data?: D;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const classNames = <Option, IsMulti extends boolean = false>(
|
||||||
|
hasError: boolean,
|
||||||
|
separators: boolean,
|
||||||
|
): ClassNamesConfig<Option, IsMulti, GroupBase<Option>> => ({
|
||||||
|
container: () => "w-full",
|
||||||
|
valueContainer: () => "text-gray-900 dark:text-white",
|
||||||
|
indicatorsContainer: () => "flex items-center self-stretch shrink-0",
|
||||||
|
clearIndicator: () => "flex items-center pr-2 text-gray-900 dark:text-white",
|
||||||
|
dropdownIndicator: () =>
|
||||||
|
"pointer-events-none flex items-center pr-2 text-gray-900 dark:text-white",
|
||||||
|
indicatorSeparator: () => "w-px text-gray-900 dark:text-white",
|
||||||
|
control: (state) =>
|
||||||
|
twMerge(
|
||||||
|
"group flex w-full items-center justify-between rounded-lg border p-3 text-sm",
|
||||||
|
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus-within:border-primary-500 focus-within:ring-primary-500",
|
||||||
|
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500",
|
||||||
|
|
||||||
|
hasError && "ring-1 ring-error",
|
||||||
|
state.isDisabled && "cursor-not-allowed",
|
||||||
|
state.isFocused && "border-primary ring ring-primary ring-opacity-50",
|
||||||
|
),
|
||||||
|
multiValue: () => "bg-fieldGray border rounded-lg flex space-x-1 pl-1 m-1",
|
||||||
|
multiValueLabel: () => "",
|
||||||
|
multiValueRemove: () => "items-center px-1 hover:text-primary",
|
||||||
|
placeholder: () =>
|
||||||
|
"block truncate pr-2 placeholder-gray dark:placeholder-gray-400",
|
||||||
|
menu: () =>
|
||||||
|
twMerge(
|
||||||
|
"!z-30 mt-2 rounded-lg border border-gray-200 bg-white p-1",
|
||||||
|
"border-gray-300 bg-gray-50 text-gray-900",
|
||||||
|
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||||
|
),
|
||||||
|
groupHeading: () => "ml-3 mt-2 mb-1 text-textGray text-sm uppercase",
|
||||||
|
option: ({ isFocused, isDisabled }) =>
|
||||||
|
twMerge(
|
||||||
|
"px-3 py-2 hover:cursor-pointer",
|
||||||
|
separators && "border-b border-gray-200",
|
||||||
|
isDisabled && "opacity-50",
|
||||||
|
isFocused && "hover:bg-primary-500 hover:text-white",
|
||||||
|
),
|
||||||
|
noOptionsMessage: () =>
|
||||||
|
"text-textGray p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm",
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SelectProps<
|
||||||
|
IsMulti extends boolean = false,
|
||||||
|
T = string,
|
||||||
|
D = unknown,
|
||||||
|
> = Prettify<
|
||||||
|
Omit<
|
||||||
|
Props<BaseOption<T, D>, IsMulti, GroupBase<BaseOption<T, D>>>,
|
||||||
|
"classNames"
|
||||||
|
> & {
|
||||||
|
required?: boolean;
|
||||||
|
separators?: boolean;
|
||||||
|
hasError?: boolean;
|
||||||
|
classNameOverrides?: ClassNamesConfig<
|
||||||
|
BaseOption<T, D>,
|
||||||
|
IsMulti,
|
||||||
|
GroupBase<BaseOption<T, D>>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const Select = <
|
||||||
|
IsMulti extends boolean = false,
|
||||||
|
T = string,
|
||||||
|
D = unknown,
|
||||||
|
>({
|
||||||
|
className,
|
||||||
|
required,
|
||||||
|
|
||||||
|
placeholder,
|
||||||
|
separators,
|
||||||
|
hasError,
|
||||||
|
options,
|
||||||
|
classNameOverrides,
|
||||||
|
...selectProps
|
||||||
|
}: SelectProps<IsMulti, T, D>) => {
|
||||||
|
const t = useTranslations("App");
|
||||||
|
return (
|
||||||
|
<ReactSelect
|
||||||
|
options={options}
|
||||||
|
noOptionsMessage={() => t("noOptionsMessage")}
|
||||||
|
placeholder={placeholder ?? t("selectPlaceholder")}
|
||||||
|
unstyled
|
||||||
|
styles={{
|
||||||
|
input: (base) => ({
|
||||||
|
...base,
|
||||||
|
"input:focus": {
|
||||||
|
boxShadow: "none",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
...classNames<BaseOption<T, D>, IsMulti>(
|
||||||
|
hasError ?? false,
|
||||||
|
separators ?? false,
|
||||||
|
),
|
||||||
|
...(classNameOverrides ?? {}),
|
||||||
|
}}
|
||||||
|
{...selectProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,14 +8,9 @@ import {
|
|||||||
FieldValues,
|
FieldValues,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
import Select, {
|
import { GroupBase, OnChangeValue } from "react-select";
|
||||||
ClassNamesConfig,
|
|
||||||
GroupBase,
|
|
||||||
OnChangeValue,
|
|
||||||
Props,
|
|
||||||
} from "react-select";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
|
import { Select, SelectProps } from "@/components/form/Select";
|
||||||
import { Prettify } from "@/types";
|
import { Prettify } from "@/types";
|
||||||
|
|
||||||
import { Field, GenericFieldProps } from "./Field";
|
import { Field, GenericFieldProps } from "./Field";
|
||||||
@@ -27,50 +22,6 @@ export type BaseOption<T = string, D = unknown> = {
|
|||||||
data?: D;
|
data?: D;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const classNames = <Option, IsMulti extends boolean = false>(
|
|
||||||
hasError: boolean,
|
|
||||||
separators: boolean,
|
|
||||||
): ClassNamesConfig<Option, IsMulti, GroupBase<Option>> => ({
|
|
||||||
container: () => "w-full",
|
|
||||||
valueContainer: () => "text-gray-900 dark:text-white",
|
|
||||||
indicatorsContainer: () => "flex items-center self-stretch shrink-0",
|
|
||||||
clearIndicator: () => "flex items-center pr-2 text-gray-900 dark:text-white",
|
|
||||||
dropdownIndicator: () =>
|
|
||||||
"pointer-events-none flex items-center pr-2 text-gray-900 dark:text-white",
|
|
||||||
indicatorSeparator: () => "w-px text-gray-900 dark:text-white",
|
|
||||||
control: (state) =>
|
|
||||||
twMerge(
|
|
||||||
"group flex w-full items-center justify-between rounded-lg border p-3",
|
|
||||||
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus-within:border-primary-500 focus-within:ring-primary-500",
|
|
||||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500",
|
|
||||||
|
|
||||||
hasError && "ring-1 ring-error",
|
|
||||||
state.isDisabled && "cursor-not-allowed",
|
|
||||||
state.isFocused && "border-primary ring ring-primary ring-opacity-50",
|
|
||||||
),
|
|
||||||
multiValue: () => "bg-fieldGray border rounded-lg flex space-x-1 pl-1 m-1",
|
|
||||||
multiValueLabel: () => "",
|
|
||||||
multiValueRemove: () => "items-center px-1 hover:text-primary",
|
|
||||||
placeholder: () =>
|
|
||||||
"block truncate pr-2 placeholder-gray dark:placeholder-gray-400",
|
|
||||||
menu: () =>
|
|
||||||
twMerge(
|
|
||||||
"!z-30 mt-2 rounded-lg border border-gray-200 bg-white p-1",
|
|
||||||
"border-gray-300 bg-gray-50 text-gray-900",
|
|
||||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
||||||
),
|
|
||||||
groupHeading: () => "ml-3 mt-2 mb-1 text-textGray text-sm uppercase",
|
|
||||||
option: ({ isFocused, isDisabled }) =>
|
|
||||||
twMerge(
|
|
||||||
"px-3 py-2 hover:cursor-pointer",
|
|
||||||
separators && "border-b border-gray-200",
|
|
||||||
isDisabled && "opacity-50",
|
|
||||||
isFocused && "hover:bg-primary-500 hover:text-white",
|
|
||||||
),
|
|
||||||
noOptionsMessage: () =>
|
|
||||||
"text-textGray p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm",
|
|
||||||
});
|
|
||||||
|
|
||||||
export type SelectFieldProps<
|
export type SelectFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
@@ -80,7 +31,7 @@ export type SelectFieldProps<
|
|||||||
> = Prettify<
|
> = Prettify<
|
||||||
GenericFieldProps<TFieldValues, TFieldName> &
|
GenericFieldProps<TFieldValues, TFieldName> &
|
||||||
Omit<
|
Omit<
|
||||||
Props<BaseOption<T, D>, IsMulti, GroupBase<BaseOption<T, D>>>,
|
SelectProps<IsMulti, T, D>,
|
||||||
"onChange" | "value" | "classNames" | "name"
|
"onChange" | "value" | "classNames" | "name"
|
||||||
> & {
|
> & {
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
@@ -168,21 +119,7 @@ export const SelectField = <
|
|||||||
value={optionValue}
|
value={optionValue}
|
||||||
onChange={onSelectChange}
|
onChange={onSelectChange}
|
||||||
options={options}
|
options={options}
|
||||||
noOptionsMessage={() => t("noOptionsMessage")}
|
hasError={hasError}
|
||||||
placeholder={placeholder ?? t("selectPlaceholder")}
|
|
||||||
unstyled
|
|
||||||
styles={{
|
|
||||||
input: (base) => ({
|
|
||||||
...base,
|
|
||||||
"input:focus": {
|
|
||||||
boxShadow: "none",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
classNames={classNames<BaseOption<T, D>, IsMulti>(
|
|
||||||
hasError,
|
|
||||||
separators ?? false,
|
|
||||||
)}
|
|
||||||
{...selectProps}
|
{...selectProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -80,7 +80,8 @@
|
|||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"searchLabel": "Search",
|
"searchLabel": "Search",
|
||||||
"searchPlaceholder": "Search",
|
"searchPlaceholder": "Search",
|
||||||
"syncWithMapCheckbox": "Tournaments visible on the map",
|
"syncWithMapOption": "Only tournaments visible in the map view",
|
||||||
|
"ignoreMapOption": "No region-based filter",
|
||||||
"normsOnly": "Only norm tournaments",
|
"normsOnly": "Only norm tournaments",
|
||||||
"noneFound": "No tournaments found",
|
"noneFound": "No tournaments found",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
|
|||||||
@@ -79,7 +79,8 @@
|
|||||||
"loading": "Téléchargement...",
|
"loading": "Téléchargement...",
|
||||||
"searchLabel": "Rechercher",
|
"searchLabel": "Rechercher",
|
||||||
"searchPlaceholder": "Rechercher",
|
"searchPlaceholder": "Rechercher",
|
||||||
"syncWithMapCheckbox": "Tournois visibles sur la carte",
|
"syncWithMapOption": "Seuls les tournois visibles dans la vue carte",
|
||||||
|
"ignoreMapOption": "Pas de filtre de région",
|
||||||
"normsOnly": "Uniquement tournois à normes",
|
"normsOnly": "Uniquement tournois à normes",
|
||||||
"noneFound": "Pas de tournois trouvé",
|
"noneFound": "Pas de tournois trouvé",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
|
|||||||
+12
-3
@@ -1,5 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
|
import { FeatureCollection } from "geojson";
|
||||||
import { omit } from "lodash";
|
import { omit } from "lodash";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,8 +8,14 @@ import { z } from "zod";
|
|||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { collections, dbConnect } from "@/server/mongodb";
|
import { collections, dbConnect } from "@/server/mongodb";
|
||||||
|
|
||||||
|
import { ZoneModel } from "./models/zoneModel";
|
||||||
import { action } from "./safeAction";
|
import { action } from "./safeAction";
|
||||||
|
|
||||||
|
export type Zone = Omit<ZoneModel, "userId" | "features"> & {
|
||||||
|
id: string;
|
||||||
|
features: FeatureCollection;
|
||||||
|
};
|
||||||
|
|
||||||
export const myZones = action(z.void(), async () => {
|
export const myZones = action(z.void(), async () => {
|
||||||
await dbConnect();
|
await dbConnect();
|
||||||
|
|
||||||
@@ -21,9 +28,11 @@ export const myZones = action(z.void(), async () => {
|
|||||||
.zones!.find({ userId: new ObjectId(user.user!.id!) })
|
.zones!.find({ userId: new ObjectId(user.user!.id!) })
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
return zones.map((zone) => ({
|
const result: Zone[] = zones.map((zone) => ({
|
||||||
...omit(zone, ["_id"]),
|
...omit(zone, ["_id", "userId"]),
|
||||||
id: zone._id.toString(),
|
id: zone._id.toString(),
|
||||||
userId: zone.userId.toString(),
|
features: zone.features as FeatureCollection,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ export default function Nodemailer(
|
|||||||
|
|
||||||
const t = await getTranslations({ locale, namespace: "SignIn" });
|
const t = await getTranslations({ locale, namespace: "SignIn" });
|
||||||
|
|
||||||
|
console.log("Connection URL", url);
|
||||||
|
|
||||||
const { host } = new URL(url);
|
const { host } = new URL(url);
|
||||||
const transport = createTransport(provider.server);
|
const transport = createTransport(provider.server);
|
||||||
const result = await transport.sendMail({
|
const result = await transport.sendMail({
|
||||||
|
|||||||
@@ -1759,6 +1759,32 @@
|
|||||||
javascript-natural-sort "0.7.1"
|
javascript-natural-sort "0.7.1"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
|
"@turf/boolean-point-in-polygon@^7.0.0-alpha.114":
|
||||||
|
version "7.0.0-alpha.114"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.0.0-alpha.114.tgz#274faa9ccc402cfa42815914040c58d9f20cc290"
|
||||||
|
integrity sha512-ytxwVqHbbBCG5DsN5QLBO6PD21EnnhnqFiGkg6a4pEOv/y7TnapSQ8RTKVJfSUdA+MhJ+uu3aSXOVH44udgNfA==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^7.0.0-alpha.114+e0bdd0add"
|
||||||
|
"@turf/invariant" "^7.0.0-alpha.114+e0bdd0add"
|
||||||
|
point-in-polygon-hao "^1.1.0"
|
||||||
|
tslib "^2.6.2"
|
||||||
|
|
||||||
|
"@turf/helpers@^7.0.0-alpha.114+e0bdd0add":
|
||||||
|
version "7.0.0-alpha.114"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-7.0.0-alpha.114.tgz#03158ecd04c147165725c2e3994bb7190c23041c"
|
||||||
|
integrity sha512-hqoARkwAaFMK/8wOWDQhmvIxjUL2l9jUhn1GUzW3fsumImPxuHoJZbIZhiHjE7ceQngveCeUCtkiKw89lekN8w==
|
||||||
|
dependencies:
|
||||||
|
deep-equal "^2.2.3"
|
||||||
|
tslib "^2.6.2"
|
||||||
|
|
||||||
|
"@turf/invariant@^7.0.0-alpha.114+e0bdd0add":
|
||||||
|
version "7.0.0-alpha.114"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-7.0.0-alpha.114.tgz#184ab4a6021839b9953b497e0aa28267947cbedb"
|
||||||
|
integrity sha512-d6KGoItyg2FFj6JSXlS23Ymvt6YA3DwAbpTwKiHCkrDP5UtHQwli+yYvmY/Bo2eC3HyDAgk/YTutmv3mNiWaJA==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^7.0.0-alpha.114+e0bdd0add"
|
||||||
|
tslib "^2.6.2"
|
||||||
|
|
||||||
"@types/aria-query@^5.0.1":
|
"@types/aria-query@^5.0.1":
|
||||||
version "5.0.4"
|
version "5.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
|
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
|
||||||
@@ -2632,7 +2658,7 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
deep-equal@^2.0.5:
|
deep-equal@^2.0.5, deep-equal@^2.2.3:
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1"
|
||||||
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
|
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
|
||||||
@@ -4663,6 +4689,11 @@ pkg-dir@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
|
point-in-polygon-hao@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/point-in-polygon-hao/-/point-in-polygon-hao-1.1.0.tgz#37f5f4fbe14e89fa8a3bb7f67c9158079d2ede7c"
|
||||||
|
integrity sha512-3hTIM2j/v9Lio+wOyur3kckD4NxruZhpowUbEgmyikW+a2Kppjtu1eN+AhnMQtoHW46zld88JiYWv6fxpsDrTQ==
|
||||||
|
|
||||||
possible-typed-array-names@^1.0.0:
|
possible-typed-array-names@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
|
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
|
||||||
@@ -5692,7 +5723,7 @@ tsconfig-paths@^3.15.0:
|
|||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
tslib@^2.1.0, tslib@^2.4.0:
|
tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||||
|
|||||||
Reference in New Issue
Block a user