✨ Add & display loves on map
This commit is contained in:
@@ -0,0 +1,313 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getDBConnexion } from '../utils';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export const GET = async (req: NextRequest) => {
|
||||||
|
const userID = req.headers.get('Authorization');
|
||||||
|
|
||||||
|
if (!userID) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pool = getDBConnexion();
|
||||||
|
|
||||||
|
const { rowCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "user" WHERE "id"=$1 LIMIT 1;`,
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
if (rowCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
`SELECT * FROM "point" WHERE "userid"=$1;`,
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
return NextResponse.json(rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Internal error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest) => {
|
||||||
|
const userID = req.headers.get('Authorization');
|
||||||
|
|
||||||
|
if (!userID) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pool = getDBConnexion();
|
||||||
|
|
||||||
|
const { rowCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "user" WHERE "id"=$1 LIMIT 1;`,
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
if (rowCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { geopoint, location, comment } = await req.json();
|
||||||
|
|
||||||
|
if (!geopoint || !location) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message:
|
||||||
|
'Missing geopoint and/or location field(s) in request body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointId = v4();
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
'INSERT INTO "point" ("id", "userid", "geopoint", "location", "comment") VALUES ($1, $2, $3, $4, $5);',
|
||||||
|
[pointId, userID, geopoint, location, comment]
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Successfully added point',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Internal error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PUT = async (req: NextRequest) => {
|
||||||
|
const userID = req.headers.get('Authorization');
|
||||||
|
|
||||||
|
if (!userID) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pool = getDBConnexion();
|
||||||
|
|
||||||
|
const { rowCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "user" WHERE "id"=$1 LIMIT 1;`,
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
if (rowCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pointId, geopoint, location, comment } = await req.json();
|
||||||
|
|
||||||
|
if (!pointId || !geopoint || !location) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message:
|
||||||
|
'Missing pointId and/or geopoint and/or location field(s) in request body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rowCount: pointsCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "point" WHERE "id"=$1 AND "userid"=$2 LIMIT 1;`,
|
||||||
|
[pointId, userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pointsCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Point not found',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
'UPDATE "point" SET "geopoint"=$1, "location"=$2, "comment"=$3 WHERE "id"=$4 AND "userid"=$5;',
|
||||||
|
[geopoint, location, comment, pointId, userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Successfully updated point',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Internal error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DELETE = async (req: NextRequest) => {
|
||||||
|
const userID = req.headers.get('Authorization');
|
||||||
|
|
||||||
|
if (!userID) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pool = getDBConnexion();
|
||||||
|
|
||||||
|
const { rowCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "user" WHERE "id"=$1 LIMIT 1;`,
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
if (rowCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Unauthorized request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pointId } = await req.json();
|
||||||
|
|
||||||
|
if (!pointId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Missing pointId field in request body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rowCount: pointsCount } = await pool.query(
|
||||||
|
`SELECT COUNT(*) FROM "point" WHERE "id"=$1 AND "userid"=$2 LIMIT 1;`,
|
||||||
|
[pointId, userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pointsCount != 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Point not found',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query('DELETE FROM "point" WHERE "id"=$1 AND "userid"=$2;', [
|
||||||
|
pointId,
|
||||||
|
userID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Successfully delete point',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
message: 'Internal error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { FormEvent } from 'react';
|
||||||
|
|
||||||
|
export const useActions = ({
|
||||||
|
viewState,
|
||||||
|
setIsAddLoveOpened,
|
||||||
|
setIsLoading,
|
||||||
|
setAddLoveMessage,
|
||||||
|
}) => {
|
||||||
|
const toggleLoveMenu = () => {
|
||||||
|
setIsAddLoveOpened((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addPoint = async (ev: FormEvent<HTMLFormElement>) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!ev.currentTarget.reportValidity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const formData = new FormData(ev.currentTarget);
|
||||||
|
const location = formData.get('location');
|
||||||
|
const comment = formData.get('comment');
|
||||||
|
|
||||||
|
const body: { [key: string]: any } = {
|
||||||
|
location: location.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comment && comment.toString().length > 0) {
|
||||||
|
body.comment = comment.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
const { latitude, longitude } = viewState;
|
||||||
|
|
||||||
|
const point = {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [longitude, latitude],
|
||||||
|
};
|
||||||
|
|
||||||
|
body.geopoint = point;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = await fetch('/api/point', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (request.status == 403) {
|
||||||
|
setIsLoading(false);
|
||||||
|
setAddLoveMessage('Reconnectez-vous pour réessayer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.status == 400) {
|
||||||
|
setIsLoading(false);
|
||||||
|
setAddLoveMessage('Erreur interne. Veuillez réessayer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setAddLoveMessage('Erreur interne. Veuillez réessayer');
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { toggleLoveMenu, addPoint };
|
||||||
|
};
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const useData = () => {
|
|
||||||
const [canUseGeolocation, setCanUseGeolocation] = useState(false);
|
|
||||||
const [geoError, setGeoError] = useState(null);
|
|
||||||
|
|
||||||
const [initialState, setInitialState] = useState({
|
|
||||||
longitude: 2.209666999999996,
|
|
||||||
latitude: 46.232192999999995,
|
|
||||||
zoom: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ('geolocation' in navigator == false) {
|
|
||||||
setCanUseGeolocation(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCanUseGeolocation(true);
|
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
position => {
|
|
||||||
setInitialState(prev => ({
|
|
||||||
...prev,
|
|
||||||
latitude: position.coords.latitude,
|
|
||||||
longitude: position.coords.longitude,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if (error.PERMISSION_DENIED) {
|
|
||||||
setGeoError('permission_denied');
|
|
||||||
console.warn(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
initialState,
|
|
||||||
geoError,
|
|
||||||
canUseGeolocation,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { IconLayer } from 'deck.gl';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { StringifiedPinIcon } from '@/assets/PinIcon';
|
||||||
|
|
||||||
|
export const useData = () => {
|
||||||
|
const deckRef = useRef(null);
|
||||||
|
|
||||||
|
const [canUseGeolocation, setCanUseGeolocation] = useState(false);
|
||||||
|
const [geoError, setGeoError] = useState(null);
|
||||||
|
|
||||||
|
const [layers, setLayers] = useState([]);
|
||||||
|
|
||||||
|
const [isAddLoveOpened, setIsAddLoveOpened] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [addLoveMessage, setAddLoveMessage] = useState(null);
|
||||||
|
|
||||||
|
const [viewState, setViewState] = useState<any>({
|
||||||
|
longitude: 2.209666999999996,
|
||||||
|
latitude: 46.232192999999995,
|
||||||
|
zoom: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ('geolocation' in navigator == false) {
|
||||||
|
setCanUseGeolocation(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanUseGeolocation(true);
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
setViewState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
latitude: position.coords.latitude,
|
||||||
|
longitude: position.coords.longitude,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (error.PERMISSION_DENIED) {
|
||||||
|
setGeoError('permission_denied');
|
||||||
|
console.warn(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const dataset = await getDataset();
|
||||||
|
|
||||||
|
const datasetLayer = new IconLayer({
|
||||||
|
id: 'lovePoints',
|
||||||
|
data: dataset,
|
||||||
|
iconAtlas: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
||||||
|
StringifiedPinIcon()
|
||||||
|
)}`,
|
||||||
|
iconMapping: {
|
||||||
|
marker: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
anchorY: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getIcon: (d) => 'marker',
|
||||||
|
getSize: (d) => 35,
|
||||||
|
getPosition: (d) => d.geometry.coordinates,
|
||||||
|
billboard: true,
|
||||||
|
pickable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayers([datasetLayer]);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
deckRef,
|
||||||
|
viewState,
|
||||||
|
setViewState,
|
||||||
|
geoError,
|
||||||
|
layers,
|
||||||
|
canUseGeolocation,
|
||||||
|
isAddLoveOpened,
|
||||||
|
setIsAddLoveOpened,
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
|
addLoveMessage,
|
||||||
|
setAddLoveMessage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDataset = async () => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
const request = await fetch('/api/point', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.json();
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
const formattedData = response.map((point) => {
|
||||||
|
const properties = { ...point };
|
||||||
|
delete properties.geopoint;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: point.geopoint,
|
||||||
|
properties: properties,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
+120
-27
@@ -1,25 +1,52 @@
|
|||||||
'use client';
|
'use client';
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Imports
|
//! Imports
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// --------------------------------------------------- Components ------------------------------------------------------
|
// --------------------------------------------------- Components ------------------------------------------------------
|
||||||
import { DeckGL } from 'deck.gl';
|
import { DeckGL } from 'deck.gl';
|
||||||
import { Map } from 'react-map-gl';
|
import { Map } from 'react-map-gl';
|
||||||
|
import Image from 'next/image';
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
|
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
|
||||||
import { useData } from './hooks/useData';
|
import { useData } from './hooks/useData';
|
||||||
|
import { useActions } from './hooks/useActions';
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
|
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
|
||||||
import HeartIcon from '@/assets/HeartIcon';
|
import HeartIcon from '@/assets/HeartIcon';
|
||||||
|
import Input from '@/components/Input/Input';
|
||||||
|
import CloseIcon from '@/assets/CloseIcon';
|
||||||
|
import AddIcon from '@/assets/AddIcon';
|
||||||
|
import pin from '@/assets/pin.png';
|
||||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
import './styles.scss';
|
import './styles.scss';
|
||||||
|
import Loader from '@/assets/Loader';
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const { canUseGeolocation, geoError, initialState } = useData();
|
const {
|
||||||
|
deckRef,
|
||||||
|
viewState,
|
||||||
|
setViewState,
|
||||||
|
geoError,
|
||||||
|
canUseGeolocation,
|
||||||
|
layers,
|
||||||
|
isAddLoveOpened,
|
||||||
|
setIsAddLoveOpened,
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
|
addLoveMessage,
|
||||||
|
setAddLoveMessage,
|
||||||
|
} = useData();
|
||||||
|
|
||||||
|
const { toggleLoveMenu, addPoint } = useActions({
|
||||||
|
viewState,
|
||||||
|
setIsAddLoveOpened,
|
||||||
|
setIsLoading,
|
||||||
|
setAddLoveMessage,
|
||||||
|
});
|
||||||
|
|
||||||
if (!canUseGeolocation && !geoError) {
|
if (!canUseGeolocation && !geoError) {
|
||||||
return <p className='appMessage'>Chargement...</p>;
|
return <p className='appMessage'>Chargement...</p>;
|
||||||
@@ -35,32 +62,98 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mapContainer'>
|
<div className='page'>
|
||||||
<DeckGL
|
<div className='mapContainer'>
|
||||||
initialViewState={initialState}
|
<DeckGL
|
||||||
layers={[]}
|
ref={deckRef}
|
||||||
controller
|
initialViewState={viewState}
|
||||||
useDevicePixels={false}
|
onViewStateChange={(e) => setViewState(e.viewState)}
|
||||||
getCursor={({ isDragging }) =>
|
layers={layers}
|
||||||
isDragging ? 'grabbing' : 'grab'
|
controller
|
||||||
}
|
useDevicePixels={false}
|
||||||
>
|
getCursor={({ isDragging }) =>
|
||||||
<Map
|
isDragging ? 'grabbing' : 'grab'
|
||||||
logoPosition='bottom-right'
|
}
|
||||||
style={{
|
>
|
||||||
width: '100%',
|
<Map
|
||||||
height: '100%',
|
logoPosition='bottom-right'
|
||||||
}}
|
style={{
|
||||||
mapboxAccessToken={process.env.MAPBOX_TOKEN}
|
width: '100%',
|
||||||
mapStyle={'mapbox://styles/mapbox/standard'}
|
height: '100%',
|
||||||
preserveDrawingBuffer
|
}}
|
||||||
></Map>
|
mapboxAccessToken={process.env.MAPBOX_TOKEN}
|
||||||
</DeckGL>
|
mapStyle={
|
||||||
|
'mapbox://styles/unepicier/cm1p8yuvq00qx01r24vg4f3ej'
|
||||||
|
}
|
||||||
|
preserveDrawingBuffer
|
||||||
|
></Map>
|
||||||
|
</DeckGL>
|
||||||
|
{isAddLoveOpened && (
|
||||||
|
<Image
|
||||||
|
src={pin}
|
||||||
|
alt='pin'
|
||||||
|
width={30}
|
||||||
|
className='pinImage'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button className='dropLove'>
|
{isAddLoveOpened && (
|
||||||
<HeartIcon />
|
<div className='addLoveContainer'>
|
||||||
Placer un love
|
<p className='addLoveHeader'>Ajouter un love ici</p>
|
||||||
</button>
|
|
||||||
|
<form
|
||||||
|
className='addLoveForm'
|
||||||
|
onSubmit={addPoint}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
name='location'
|
||||||
|
placeholder="C'était où ?"
|
||||||
|
autoComplete='off'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
name='comment'
|
||||||
|
placeholder='Un commentaire'
|
||||||
|
autoComplete='off'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='buttonsContainer'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='button'
|
||||||
|
onClick={toggleLoveMenu}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='button'
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isLoading ? <Loader /> : <AddIcon />}
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isAddLoveOpened && (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='dropLove'
|
||||||
|
onClick={toggleLoveMenu}
|
||||||
|
>
|
||||||
|
<HeartIcon />
|
||||||
|
Placer un love
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+61
-1
@@ -1,6 +1,20 @@
|
|||||||
.mapContainer {
|
.page {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
& > .mapContainer {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .pinImage {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& > .dropLove {
|
& > .dropLove {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -18,6 +32,52 @@
|
|||||||
background-color: #d94253;
|
background-color: #d94253;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.6);
|
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.6);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .addLoveContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
|
||||||
|
& > .addLoveHeader {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #d94253;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .addLoveForm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
& > .buttonsContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
& > .button {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20px max-content;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #d94253;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
const AddIcon = (props: SVGProps<SVGSVGElement>) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 512 512'
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
strokeWidth='32'
|
||||||
|
d='M256 112v288M400 256H112'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddIcon;
|
||||||
+1
-1
@@ -10,7 +10,7 @@ const Loader = (props?: SVGProps<SVGSVGElement>) => {
|
|||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
fill='none'
|
fill='none'
|
||||||
fill-rule='evenodd'
|
fillRule='evenodd'
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
transform='translate(1 1)'
|
transform='translate(1 1)'
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const PinIcon = (props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
version='1.1'
|
||||||
|
x='0px'
|
||||||
|
y='0px'
|
||||||
|
viewBox='0 0 100 100'
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<path d='M82.1,34.9C80.9,19.2,68.1,6.5,52.3,5.4C33.5,4.1,17.8,19,17.8,37.6c0,6.7,2,12.9,5.5,18c4.2,6.2,16.2,23.7,22.6,33.1 c2,2.9,6.3,2.9,8.2,0c6.4-9.3,18.4-26.9,22.6-33.1C80.7,49.8,82.7,42.6,82.1,34.9z M50,56c-10.2,0-18.5-8.3-18.5-18.5 S39.8,19,50,19c10.2,0,18.5,8.3,18.5,18.5S60.2,56,50,56z' />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PinIcon;
|
||||||
|
|
||||||
|
export const StringifiedPinIcon = () => {
|
||||||
|
return `
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
version='1.1'
|
||||||
|
width='100'
|
||||||
|
height='100'
|
||||||
|
viewBox='0 0 100 100'
|
||||||
|
fill='#d94253'
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d='M82.1,34.9C80.9,19.2,68.1,6.5,52.3,5.4C33.5,4.1,17.8,19,17.8,37.6c0,6.7,2,12.9,5.5,18c4.2,6.2,16.2,23.7,22.6,33.1 c2,2.9,6.3,2.9,8.2,0c6.4-9.3,18.4-26.9,22.6-33.1C80.7,49.8,82.7,42.6,82.1,34.9z M50,56c-10.2,0-18.5-8.3-18.5-18.5 S39.8,19,50,19c10.2,0,18.5,8.3,18.5,18.5S60.2,56,50,56z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
Generated
+15
-1
@@ -19,7 +19,8 @@
|
|||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-map-gl": "^7.1.7",
|
"react-map-gl": "^7.1.7",
|
||||||
"react-slot-counter": "^3.0.1"
|
"react-slot-counter": "^3.0.1",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
@@ -8430,6 +8431,19 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vt-pbf": {
|
"node_modules/vt-pbf": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||||
|
|||||||
+2
-1
@@ -20,7 +20,8 @@
|
|||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-map-gl": "^7.1.7",
|
"react-map-gl": "^7.1.7",
|
||||||
"react-slot-counter": "^3.0.1"
|
"react-slot-counter": "^3.0.1",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user