diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts new file mode 100644 index 0000000..0382b86 --- /dev/null +++ b/app/api/auth/login/route.ts @@ -0,0 +1,71 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDBConnexion } from '../../utils'; +import { compareSync } from 'bcrypt'; + +type RequestBody = { + username?: string; + password?: string; +}; + +export const POST = async (req: NextRequest) => { + const { username, password }: RequestBody = await req.json(); + + if (!username || !password) { + return NextResponse.json( + { + error: true, + message: 'Missing username or password field in request body', + }, + { + status: 400, + } + ); + } + + try { + const pool = getDBConnexion(); + + const { rows } = await pool.query( + `SELECT * FROM "user" WHERE "username"=$1 LIMIT 1;`, + [username] + ); + + await pool.end(); + + if (rows.length != 1) { + return NextResponse.json({ + error: true, + message: 'User not found', + }); + } + + const { id, password: hashedPassword } = rows[0]; + + if (!compareSync(password, hashedPassword)) { + return NextResponse.json( + { + error: true, + message: 'Wrong password', + }, + { + status: 403, + } + ); + } + + return NextResponse.json({ + token: id, + }); + } catch (error) { + console.error(error); + return NextResponse.json( + { + error: true, + message: 'Internal error', + }, + { + status: 500, + } + ); + } +}; diff --git a/app/api/user/route.ts b/app/api/user/route.ts new file mode 100644 index 0000000..bcb29b0 --- /dev/null +++ b/app/api/user/route.ts @@ -0,0 +1,285 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDBConnexion } from '../utils'; +import { compareSync, hashSync } from 'bcrypt'; + +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 { rows } = await pool.query( + `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`, + [userID] + ); + + if (rows.length != 1) { + return NextResponse.json( + { + error: true, + message: 'User not found', + }, + { + status: 404, + } + ); + } + + await pool.end(); + + return NextResponse.json(rows[0]); + } catch (error) { + console.error(error); + return NextResponse.json( + { + error: true, + message: 'Internal error', + }, + { + status: 500, + } + ); + } +}; + +type RequestBody = { + type?: 'username' | 'password'; + username?: string; + currentPassword?: string; + newPassword?: string; +}; + +export const PUT = async (req: NextRequest) => { + const userID = req.headers.get('Authorization'); + + const { type, username, currentPassword, newPassword }: RequestBody = + await req.json(); + + if (!userID) { + return NextResponse.json( + { + error: true, + message: 'Unauthorized request', + }, + { + status: 403, + } + ); + } + + if (!type) { + return NextResponse.json( + { + error: true, + message: 'Missing type field in request body', + }, + { + status: 400, + } + ); + } + + if (type == 'username') { + if (!username) { + return NextResponse.json( + { + error: true, + message: 'Missing username field in request body', + }, + { + status: 400, + } + ); + } + + try { + const pool = getDBConnexion(); + + const { rowCount } = await pool.query( + `SELECT COUNT(*) FROM "user" WHERE "username"=$1 LIMIT 1;`, + [username] + ); + + if (rowCount > 0) { + return NextResponse.json( + { + error: true, + message: 'Username already used', + }, + { + status: 409, + } + ); + } + + const { rows } = await pool.query( + `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`, + [userID] + ); + + if (rows.length != 1) { + return NextResponse.json( + { + error: true, + message: 'User not found', + }, + { + status: 404, + } + ); + } + + await pool.query(`UPDATE "user" SET "username"=$1 WHERE "id"=$2;`, [ + username, + userID, + ]); + + await pool.end(); + + return NextResponse.json({ + message: 'Successfully updated username', + }); + } catch (error) { + console.error(error); + return NextResponse.json( + { + error: true, + message: 'Internal error', + }, + { + status: 500, + } + ); + } + } + + if (type == 'password') { + if (!currentPassword || !newPassword) { + return NextResponse.json( + { + error: true, + message: + 'Missing currentPassword and/or newPassword field(s) in request body', + }, + { + status: 400, + } + ); + } + + try { + const pool = getDBConnexion(); + + const { rows } = await pool.query( + `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`, + [userID] + ); + + if (rows.length != 1) { + return NextResponse.json( + { + error: true, + message: 'User not found', + }, + { + status: 404, + } + ); + } + + const { password: currentHashedPassword } = rows[0]; + + if (!compareSync(currentPassword, currentHashedPassword)) { + return NextResponse.json( + { + error: true, + message: 'Wrong password', + }, + { + status: 403, + } + ); + } + + const newHashedPassword = hashSync(newPassword, 10); + + await pool.query(`UPDATE "user" SET "password"=$1 WHERE "id"=$2`, [ + newHashedPassword, + userID, + ]); + + await pool.end(); + + return NextResponse.json({ + message: 'Successfully updated password', + }); + } 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'); + + try { + const pool = getDBConnexion(); + + const { rows } = await pool.query( + `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`, + [userID] + ); + + if (rows.length != 1) { + return NextResponse.json( + { + error: true, + message: 'User not found', + }, + { + status: 404, + } + ); + } + + await pool.query(`DELETE FROM "user" WHERE "id"=$1 LIMIT 1;`, [userID]); + + await pool.end(); + + return NextResponse.json({ + message: 'Successfully delete user', + }); + } catch (error) { + console.error(error); + return NextResponse.json( + { + error: true, + message: 'Internal error', + }, + { + status: 500, + } + ); + } +}; diff --git a/app/api/utils.ts b/app/api/utils.ts new file mode 100644 index 0000000..fa8c22c --- /dev/null +++ b/app/api/utils.ts @@ -0,0 +1,9 @@ +import { createPool } from '@vercel/postgres'; + +export const getDBConnexion = () => { + const pool = createPool({ + connectionString: process.env.POSTGRES_URL, + }); + + return pool; +}; diff --git a/app/auth/login/hooks/useActions.js b/app/auth/login/hooks/useActions.js index f4139ed..998c9e6 100644 --- a/app/auth/login/hooks/useActions.js +++ b/app/auth/login/hooks/useActions.js @@ -17,19 +17,16 @@ export const useActions = ({ setError, setIsLoading }) => { setIsLoading(true); try { - const request = await fetch( - 'https://lovemap-backend.vercel.app/auth/login', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - username, - password, - }), - } - ); + const request = await fetch('/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + }), + }); const response = await request.json(); @@ -44,7 +41,6 @@ export const useActions = ({ setError, setIsLoading }) => { return; } if (request.status == 500) { - console.log('request.status: ', request.status); setError('Erreur interne. Veuillez réessayer'); setIsLoading(false); return; diff --git a/app/auth/login/page.jsx b/app/auth/login/page.jsx index dd76ab2..2cde739 100644 --- a/app/auth/login/page.jsx +++ b/app/auth/login/page.jsx @@ -18,7 +18,7 @@ import { useActions } from './hooks/useActions'; // ------------------------------------------------- Assets & Styles --------------------------------------------------- import Loader from '@/assets/Loader'; -import '../styles.scss'; +import './styles.scss'; // --------------------------------------------------------------------------------------------------------------------- const Login = () => { diff --git a/app/auth/styles.scss b/app/auth/login/styles.scss similarity index 99% rename from app/auth/styles.scss rename to app/auth/login/styles.scss index 44ff915..6908a00 100644 --- a/app/auth/styles.scss +++ b/app/auth/login/styles.scss @@ -39,7 +39,7 @@ display: flex; flex-direction: column; align-items: stretch; - gap: 15px; + gap: 20px; & > .error { text-align: center; diff --git a/app/globals.scss b/app/globals.scss index 9d39b0e..cd25b40 100644 --- a/app/globals.scss +++ b/app/globals.scss @@ -18,7 +18,7 @@ body { flex-direction: column; width: 100svw; height: 100svh; - background-color: #d94253; + background-color: #ffffff; } a { diff --git a/app/layout.jsx b/app/layout.jsx deleted file mode 100644 index e85d807..0000000 --- a/app/layout.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Inter } from 'next/font/google'; -import Provider from './provider'; -import './globals.scss'; - -const inter = Inter({ subsets: ['latin'] }); - -export const metadata = { - title: 'Lovemap ❤️', -}; - -const RootLayout = ({ children }) => { - return ( - - - {children} - - - ); -}; - -export default RootLayout; diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..4c029c3 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,35 @@ +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------ Next --------------------------------------------------------- +import { Metadata } from 'next'; +// --------------------------------------------------------------------------------------------------------------------- + +// --------------------------------------------------- Components ------------------------------------------------------ +import Provider from './provider'; +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Assets & Styles --------------------------------------------------- +import { Inter } from 'next/font/google'; +import './globals.scss'; +// --------------------------------------------------------------------------------------------------------------------- + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'Lovemap ❤️', + robots: 'none', +}; + +const RootLayout = ({ children }) => { + return ( + + + {children} + + + ); +}; + +export default RootLayout; diff --git a/app/not-found.jsx b/app/not-found.tsx similarity index 100% rename from app/not-found.jsx rename to app/not-found.tsx diff --git a/app/page.jsx b/app/page.tsx similarity index 100% rename from app/page.jsx rename to app/page.tsx diff --git a/app/provider.jsx b/app/provider.tsx similarity index 100% rename from app/provider.jsx rename to app/provider.tsx diff --git a/app/settings/hooks/useActions.ts b/app/settings/hooks/useActions.ts new file mode 100644 index 0000000..ad182df --- /dev/null +++ b/app/settings/hooks/useActions.ts @@ -0,0 +1,159 @@ +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// -------------------------------------------------- Hooks & Utils ---------------------------------------------------- +import { useRouter } from 'next/navigation'; +import { FormEvent } from 'react'; +// --------------------------------------------------------------------------------------------------------------------- + +export const useActions = ({ + userData, + setIsPopupDisplayed, + setUsernameMessage, + setPasswordMessage, +}) => { + const router = useRouter(); + + const resetMessage = () => { + setUsernameMessage(null); + setPasswordMessage(null); + }; + + const updateUsername = async (value: string) => { + resetMessage(); + + if (value == userData.username) { + return; + } + + const token = localStorage.getItem('token'); + + try { + const request = await fetch('/api/user', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + body: JSON.stringify({ + type: 'username', + username: value, + }), + }); + + if (request.status == 409) { + setUsernameMessage("Nom d'utilisateur déjà pris"); + return; + } + + if (request.status != 200) { + setUsernameMessage('Erreur interne. Veuillez réessayer'); + return; + } + setUsernameMessage("Nom d'utilisateur modifié"); + } catch (error) { + console.error(error); + + setUsernameMessage('Erreur interne. Veuillez réessayer'); + } + }; + + const updatePassword = async (ev: FormEvent) => { + ev.preventDefault(); + + if (!ev.currentTarget.reportValidity()) { + return; + } + + resetMessage(); + + const formData = new FormData(ev.currentTarget); + const currentPassword = formData.get('currentPassword'); + const newPassword = formData.get('newPassword'); + const confirmNewPassword = formData.get('confirmNewPassword'); + + const token = localStorage.getItem('token'); + + if (newPassword != confirmNewPassword) { + setPasswordMessage('Les mots de passes ne correspondent pas'); + return; + } + + try { + const request = await fetch('/api/user', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + body: JSON.stringify({ + type: 'password', + currentPassword, + newPassword, + }), + }); + + if (request.status == 403) { + setPasswordMessage('Mauvais mot de passe'); + return; + } + if (request.status == 500) { + setPasswordMessage('Erreur interne. Veuillez réessayer'); + return; + } + setPasswordMessage('Mot de passe modifié'); + } catch (error) { + console.error(error); + + setPasswordMessage('Erreur interne. Veuillez réessayer'); + } + }; + + const handleClosePopup = (ev) => { + ev.stopPropagation(); + setIsPopupDisplayed(false); + }; + + const handleDeleteUser = (ev) => { + ev.stopPropagation(); + resetMessage(); + setIsPopupDisplayed(true); + }; + + const deleteUser = async () => { + resetMessage(); + const token = localStorage.getItem('token'); + + try { + const request = await fetch( + 'https://lovemap-backend.vercel.app/user/delete', + { + method: 'DELETE', + headers: { + Authorization: token, + }, + } + ); + + if (request.status != 200) { + // setMessage('Erreur interne. Veuillez réessayer'); + return; + } + + localStorage.clear(); + router.push('/auth/login'); + } catch (error) { + console.error(error); + // setMessage('Erreur interne. Veuillez réessayer'); + } + }; + + return { + updateUsername, + updatePassword, + handleClosePopup, + handleDeleteUser, + deleteUser, + }; +}; diff --git a/app/settings/hooks/useData.ts b/app/settings/hooks/useData.ts new file mode 100644 index 0000000..1e89f47 --- /dev/null +++ b/app/settings/hooks/useData.ts @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; + +export const useData = () => { + const [userData, setUserData] = useState(null); + const [isPopupDisplayed, setIsPopupDisplayed] = useState(false); + const [usernameMessage, setUsernameMessage] = useState(null); + const [passwordMessage, setPasswordMessage] = useState(null); + + useEffect(() => { + (async () => { + const token = localStorage.getItem('token'); + + const request = await fetch('/api/user', { + method: 'GET', + headers: { + Authorization: token, + }, + }); + + const response = await request.json(); + + const formattedResponse = { + ...response, + createdat: new Date(response.createdat).toLocaleDateString( + 'fr-FR' + ), + }; + + setUserData(formattedResponse); + })(); + }, []); + + return { + userData, + isPopupDisplayed, + setIsPopupDisplayed, + usernameMessage, + setUsernameMessage, + passwordMessage, + setPasswordMessage, + }; +}; diff --git a/app/settings/page.jsx b/app/settings/page.jsx deleted file mode 100644 index d8d1c3b..0000000 --- a/app/settings/page.jsx +++ /dev/null @@ -1,73 +0,0 @@ -// --------------------------------------------------------------------------------------------------------------------- -//! Imports -// --------------------------------------------------------------------------------------------------------------------- - -// --------------------------------------------------- Components ------------------------------------------------------ -import Link from 'next/link'; -import Input from '@/components/Input/Input'; -// --------------------------------------------------------------------------------------------------------------------- - -// ------------------------------------------------- Assets & Styles --------------------------------------------------- -import ArrowRightIcon from '@/assets/ArrowRightIcon'; -import './styles.scss'; -// --------------------------------------------------------------------------------------------------------------------- - -const Settings = () => { - return ( -
-
- - - -

Éditer le profil

-
- -
- - - -
- - -
-
- -
- - -
- ); -}; - -export default Settings; diff --git a/app/settings/page.tsx b/app/settings/page.tsx new file mode 100644 index 0000000..cd015fd --- /dev/null +++ b/app/settings/page.tsx @@ -0,0 +1,187 @@ +'use client'; + +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// --------------------------------------------------- Components ------------------------------------------------------ +import Link from 'next/link'; +import Input from '@/components/Input/Input'; +import InlineInput from '@/components/InlineInput/InlineInput'; +// --------------------------------------------------------------------------------------------------------------------- + +// -------------------------------------------------- Hooks & Utils ---------------------------------------------------- +import { useData } from './hooks/useData'; +import { useActions } from './hooks/useActions'; +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Assets & Styles --------------------------------------------------- +import ArrowRightIcon from '@/assets/ArrowRightIcon'; +import TrashIcon from '@/assets/TrashIcon'; +import CloseIcon from '@/assets/CloseIcon'; +import CheckIcon from '@/assets/CheckIcon'; +import './styles.scss'; +// --------------------------------------------------------------------------------------------------------------------- + +const Settings = () => { + const { + userData, + isPopupDisplayed, + setIsPopupDisplayed, + usernameMessage, + setUsernameMessage, + passwordMessage, + setPasswordMessage, + } = useData(); + + const { + updateUsername, + updatePassword, + handleClosePopup, + handleDeleteUser, + deleteUser, + } = useActions({ + userData, + setIsPopupDisplayed, + setUsernameMessage, + setPasswordMessage, + }); + + if (!userData) { + return; + } + + return ( +
+
+ + + +

Éditer le profil

+
+ +
+ {usernameMessage && ( +

{usernameMessage}

+ )} + + + +
+ +
+ {passwordMessage && ( +

{passwordMessage}

+ )} + + + + + +
+ +
+ + + + {isPopupDisplayed && ( +
+
ev.stopPropagation()} + > +
+

+ Supprimer le compte ? +

+ +
+
+

+ Êtes-vous sûr de vouloir supprimer votre + compte ? Cette action est irréversible. +

+
+
+ + + +
+
+
+ )} +
+
+ ); +}; + +export default Settings; diff --git a/app/settings/styles.scss b/app/settings/styles.scss index 2b22227..ebc37cb 100644 --- a/app/settings/styles.scss +++ b/app/settings/styles.scss @@ -4,7 +4,6 @@ flex-direction: column; gap: 20px; width: 100%; - background-color: #d94253; & > .header { display: grid; @@ -12,6 +11,7 @@ align-items: center; gap: 15px; padding: 15px; + background-color: #d94253; color: #ffffff; box-shadow: 0 0 6px rgba(255, 255, 255, 0.2); @@ -26,68 +26,147 @@ } } - & > .form { - display: grid; - grid-template-rows: repeat(2, 1fr); - align-items: center; + & > .settingsContainer { + flex-grow: 1; + position: relative; + display: flex; + flex-direction: column; gap: 20px; - padding: 20px; - padding-bottom: 0; + padding: 0 20px; - & > .buttonsContainer { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; + & .message { + padding: 5px; + border-radius: 5px; + background-color: #d94253; + font-size: 1rem; + text-align: center; + color: #ffffff; + } - & > .submitButton, - & > .resetButton { - display: flex; - justify-content: center; - align-items: center; - gap: 5px; - padding: 10px; - border: 1px solid #ffffff; + & > .separator { + display: block; + margin: 20px auto; + width: 90%; + height: 1px; + background-color: #d94253; + background: linear-gradient( + 90deg, + rgba(#d94253, 0) 0%, + rgba(#d94253, 1) 10%, + rgba(#d94253, 1) 90%, + rgba(#d94253, 0) 100% + ); + } + + & > .passwordForm { + display: flex; + flex-direction: column; + gap: 10px; + + & > .submitButton { + padding: 7px 21px; + border: none; border-radius: 5px; - background-color: #ffffff; + background-color: #d94253; font-size: 1rem; - color: #d94253; + color: #ffffff; cursor: pointer; } + } - & > .resetButton { - background-color: #d94253; - color: #ffffff; + & > .deleteButton { + display: grid; + grid-template-columns: 20px max-content; + justify-content: center; + align-items: center; + gap: 10px; + padding: 7px 21px; + border: 1px solid #d94253; + border-radius: 5px; + background-color: #d94253; + font-size: 1rem; + color: #ffffff; + cursor: pointer; + } + + & > .popupOverlay { + position: absolute; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + background-color: rgba(#ffffff, 0.5); + backdrop-filter: blur(5px); + + & > .popup { + z-index: 1; + position: relative; + display: flex; + flex-direction: column; + margin: 10px; + background-color: #ffffff; + border: 1px solid #d94253; + border-radius: 5px; + + & > .popupHeader { + display: grid; + grid-template-columns: 1fr 20px; + align-items: center; + gap: 20px; + padding: 10px; + + & > .popupTitle { + font-size: 1.2rem; + color: #d94253; + } + + & > .popupCloseButton { + display: flex; + justify-content: center; + align-items: center; + border: none; + border-radius: 5px; + background-color: transparent; + color: #d94253; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: rgba(#d94253, 0.2); + } + } + } + + & > .popupContent { + padding: 10px; + color: #d94253; + } + + & > .popupButtonsContainer { + display: flex; + flex-wrap: nowrap; + justify-content: space-evenly; + align-items: center; + padding: 10px; + + & > .popupButton { + display: grid; + grid-template-columns: 20px 1fr; + align-items: center; + gap: 5px; + padding: 5px 15px; + border: none; + border-radius: 5px; + background-color: #d94253; + font-size: 1rem; + color: #ffffff; + cursor: pointer; + } + } } } } - - & > .separator { - display: block; - margin: 20px auto; - width: 90%; - height: 1px; - background-color: rgb(254, 254, 250); - background: linear-gradient( - 90deg, - rgba(254, 254, 250, 0) 0%, - rgba(254, 254, 250, 1) 10%, - rgba(254, 254, 250, 1) 90%, - rgba(254, 254, 250, 0) 100% - ); - } - - & > .deleteButton { - display: flex; - justify-content: center; - align-items: center; - gap: 5px; - margin: 0 20px; - padding: 10px; - border: 1px solid #d80135; - border-radius: 5px; - background-color: #d80135; - font-size: 1rem; - color: #ffffff; - cursor: pointer; - } } diff --git a/app/statistics/hooks/useData.js b/app/statistics/hooks/useData.js index 638e12c..da26de8 100644 --- a/app/statistics/hooks/useData.js +++ b/app/statistics/hooks/useData.js @@ -7,21 +7,18 @@ export const useData = () => { (async () => { const token = localStorage.getItem('token'); - const request = await fetch( - 'https://lovemap-backend.vercel.app/user', - { - method: 'GET', - headers: { - Authorization: token, - }, - } - ); + const request = await fetch('/api/user', { + method: 'GET', + headers: { + Authorization: token, + }, + }); const response = await request.json(); const formattedResponse = { - ...response.user, - createdat: new Date(response.user.createdat).toLocaleDateString( + ...response, + createdat: new Date(response.createdat).toLocaleDateString( 'fr-FR' ), }; diff --git a/app/statistics/page.jsx b/app/statistics/page.tsx similarity index 82% rename from app/statistics/page.jsx rename to app/statistics/page.tsx index 3636e8e..cfcfc73 100644 --- a/app/statistics/page.jsx +++ b/app/statistics/page.tsx @@ -10,21 +10,29 @@ import SlotCounter from 'react-slot-counter'; // --------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------- Hooks & Utils ---------------------------------------------------- +import { useRouter } from 'next/navigation'; import { useData } from './hooks/useData'; // --------------------------------------------------------------------------------------------------------------------- // ------------------------------------------------- Assets & Styles --------------------------------------------------- import SettingsIcon from '@/assets/SettingsIcon'; +import { Logout } from '@/assets/Logout'; import './styles.scss'; // --------------------------------------------------------------------------------------------------------------------- const Statistics = () => { + const router = useRouter(); const { userData } = useData(); if (!userData) { return; } + const logout = () => { + localStorage.clear(); + router.push('/auth/login'); + }; + return (
@@ -33,12 +41,21 @@ const Statistics = () => {

Créé le: {userData.createdat}

- - - +
+ + + + +
@@ -48,7 +65,7 @@ const Statistics = () => { startValue={'00'} startValueOnce dummyCharacterCount={10} - direction='top-bottom' + direction='top-down' containerClassName='slotCounter' />

Loves par semaine

@@ -59,7 +76,7 @@ const Statistics = () => { startValue={'00'} startValueOnce dummyCharacterCount={10} - direction='top-bottom' + direction='top-down' containerClassName='slotCounter' />

Loves par mois

@@ -70,7 +87,7 @@ const Statistics = () => { startValue={'00'} startValueOnce dummyCharacterCount={10} - direction='top-bottom' + direction='top-down' containerClassName='slotCounter' />

Loves par an

@@ -81,7 +98,7 @@ const Statistics = () => { startValue={'00'} startValueOnce dummyCharacterCount={10} - direction='top-bottom' + direction='top-down' containerClassName='slotCounter' />

Loves total

diff --git a/app/statistics/styles.scss b/app/statistics/styles.scss index 3a26263..f191827 100644 --- a/app/statistics/styles.scss +++ b/app/statistics/styles.scss @@ -21,21 +21,31 @@ & > .username { font-size: 1.1rem; - color: #ffffff; + color: #d94253; } & > .sinceDate { font-size: 0.9rem; font-weight: 300; - color: #eee; + color: #d94253; } } - & > .settingsButton { - $size: 25px; - width: $size; - height: $size; - color: #ffffff; + & > .buttonsContainer { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 10px; + + & > .button { + $size: 25px; + width: $size; + height: $size; + border: none; + background-color: transparent; + color: #d94253; + cursor: pointer; + } } } @@ -43,13 +53,13 @@ display: block; width: 90%; height: 1px; - background-color: rgb(254, 254, 250); + background-color: #d94253; background: linear-gradient( 90deg, - rgba(254, 254, 250, 0) 0%, - rgba(254, 254, 250, 1) 10%, - rgba(254, 254, 250, 1) 90%, - rgba(254, 254, 250, 0) 100% + rgba(#d94253, 0) 0%, + rgba(#d94253, 1) 10%, + rgba(#d94253, 1) 90%, + rgba(#d94253, 0) 100% ); } @@ -68,45 +78,14 @@ flex-direction: column; align-items: center; font-weight: 300; - color: #ffffff; + color: #d94253; & > .slotCounter { font-size: 3rem; } & > .statName { - color: #eee; - } - } - } - - & > .buttonsContainer { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 2px; - width: 100%; - box-shadow: 0 0 6px rgba(255, 255, 255, 0.2); - - & > .button { - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - gap: 20px; - padding: 15px 20px; - border: none; - background-color: #d94253; - font-size: 1rem; - color: #ffffff; - filter: brightness(1.1); - transition: filter 0.3s ease; - - &:hover { - filter: brightness(1.3); - } - - & > svg { - width: 1.2em; + color: #d94253; } } } diff --git a/assets/ArrowRightIcon.jsx b/assets/ArrowRightIcon.tsx similarity index 76% rename from assets/ArrowRightIcon.jsx rename to assets/ArrowRightIcon.tsx index df46e8c..a153108 100644 --- a/assets/ArrowRightIcon.jsx +++ b/assets/ArrowRightIcon.tsx @@ -1,4 +1,6 @@ -const ArrowRightIcon = (props) => { +import { SVGProps } from 'react'; + +const ArrowRightIcon = (props?: SVGProps) => { return ( ) => { + return ( + + + + ); +}; + +export default CheckIcon; diff --git a/assets/CloseIcon.tsx b/assets/CloseIcon.tsx new file mode 100644 index 0000000..3b5de6b --- /dev/null +++ b/assets/CloseIcon.tsx @@ -0,0 +1,22 @@ +import { SVGProps } from 'react'; + +const CloseIcon = (props: SVGProps) => { + return ( + + + + ); +}; + +export default CloseIcon; diff --git a/assets/EyeIcon.jsx b/assets/EyeIcon.tsx similarity index 87% rename from assets/EyeIcon.jsx rename to assets/EyeIcon.tsx index 33e736b..7ac7dff 100644 --- a/assets/EyeIcon.jsx +++ b/assets/EyeIcon.tsx @@ -1,4 +1,6 @@ -const EyeIcon = (props) => { +import { SVGProps } from 'react'; + +const EyeIcon = (props?: SVGProps) => { return ( { +import { SVGProps } from 'react'; + +const EyeSlashIcon = (props?: SVGProps) => { return ( diff --git a/assets/HeartIcon.jsx b/assets/HeartIcon.tsx similarity index 84% rename from assets/HeartIcon.jsx rename to assets/HeartIcon.tsx index 8f57e4d..ee2cdce 100644 --- a/assets/HeartIcon.jsx +++ b/assets/HeartIcon.tsx @@ -1,4 +1,6 @@ -const HeartIcon = (props) => { +import { SVGProps } from 'react'; + +const HeartIcon = (props?: SVGProps) => { return ( { +import { SVGProps } from 'react'; + +const Loader = (props?: SVGProps) => { return ( ) => { + return ( + + + + ); +}; diff --git a/assets/MapIcon.jsx b/assets/MapIcon.tsx similarity index 86% rename from assets/MapIcon.jsx rename to assets/MapIcon.tsx index f56c4be..bc27b12 100644 --- a/assets/MapIcon.jsx +++ b/assets/MapIcon.tsx @@ -1,4 +1,6 @@ -const MapIcon = (props) => { +import { SVGProps } from 'react'; + +const MapIcon = (props?: SVGProps) => { return ( { +import { SVGProps } from 'react'; + +const SettingsIcon = (props?: SVGProps) => { return ( { +import { SVGProps } from 'react'; + +const StatsIcon = (props?: SVGProps) => { return ( ) => { + return ( + + + + + + ); +}; + +export default TrashIcon; diff --git a/components/InlineInput/InlineInput.tsx b/components/InlineInput/InlineInput.tsx new file mode 100644 index 0000000..02810a6 --- /dev/null +++ b/components/InlineInput/InlineInput.tsx @@ -0,0 +1,80 @@ +'use client'; + +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------ React -------------------------------------------------------- +import { FormEvent } from 'react'; +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Assets & Styles --------------------------------------------------- +import CheckIcon from '@/assets/CheckIcon'; +import CloseIcon from '@/assets/CloseIcon'; +import './styles.scss'; +// --------------------------------------------------------------------------------------------------------------------- + +const InlineInput = ({ + label = '', + name = '', + type = 'text', + placeholder = '', + defaultValue = '', + autoComplete = 'off', + onSubmit = (value?: string) => {}, +}) => { + const handleSubmit = (ev: FormEvent) => { + ev.preventDefault(); + + if (!ev.currentTarget.reportValidity()) { + return; + } + + const formData = new FormData(ev.currentTarget); + + const value = formData.get(name); + + onSubmit(value ? value.toString() : null); + }; + + return ( +
+ + +
+ + + +
+ + ); +}; + +export default InlineInput; diff --git a/components/InlineInput/styles.scss b/components/InlineInput/styles.scss new file mode 100644 index 0000000..f213c33 --- /dev/null +++ b/components/InlineInput/styles.scss @@ -0,0 +1,50 @@ +.inlineInputContainer { + display: flex; + flex-direction: column; + gap: 5px; + + & > .inlineInputLabel { + margin-left: 5px; + font-size: 1.1rem; + color: #d94253; + } + + & > .inlineInputRow { + display: grid; + grid-template-columns: 1fr repeat(2, calc(1rem + 14px)); + align-items: center; + gap: 5px; + + & > .inlineInput { + padding: 7px 14px; + border: 1px solid #d94253; + border-radius: 5px; + background-color: #f5cfd3; + outline: none; + font-size: 0.9rem; + color: #d94253; + outline: none; + + &::placeholder { + color: #d94253; + } + } + + & > .inlineInputButton { + display: flex; + justify-content: center; + align-items: center; + padding: 5px; + border: none; + border-radius: 5px; + background-color: transparent; + color: #d94253; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: rgba(#d94253, 0.5); + } + } + } +} diff --git a/components/Input/Input.jsx b/components/Input/Input.tsx similarity index 59% rename from components/Input/Input.jsx rename to components/Input/Input.tsx index 8805924..14762bb 100644 --- a/components/Input/Input.jsx +++ b/components/Input/Input.tsx @@ -5,7 +5,7 @@ // --------------------------------------------------------------------------------------------------------------------- // ------------------------------------------------------ React -------------------------------------------------------- -import { forwardRef, useState } from 'react'; +import { forwardRef, MouseEvent, useState } from 'react'; // --------------------------------------------------------------------------------------------------------------------- // ------------------------------------------------- Assets & Styles --------------------------------------------------- @@ -14,75 +14,51 @@ import EyeIcon from '@/assets/EyeIcon'; import './styles.scss'; // --------------------------------------------------------------------------------------------------------------------- -const Input = forwardRef(function InputComponent( - { - type = 'text', - label, - name, - value, - defaultValue, - autoComplete, - required = false, - onChange, - onKeyUp, - onKeyDown, - }, - ref -) { - const [isFocused, setIsFocused] = useState( - defaultValue || value ? true : false - ); - const [passwordVisibility, setPasswordVisibility] = useState(false); +const Input = ({ + name = '', + type = '', + placeholder = '', + defaultValue = '', + required = false, + autoComplete = 'off', +}) => { + const [isPasswordVisible, setIsPasswordVisible] = useState(false); - const defaultOnBlur = (ev) => { - if (ev.currentTarget.value != '') { - return; - } + const togglePasswordVisibility = (ev: MouseEvent) => { + ev.stopPropagation(); - setIsFocused(false); + setIsPasswordVisible((prev) => !prev); }; return (
setIsFocused(true)} - onBlur={defaultOnBlur} /> - {type == 'password' && ( )}
); -}); +}; export default Input; diff --git a/components/Input/styles.scss b/components/Input/styles.scss index a8ab716..e172972 100644 --- a/components/Input/styles.scss +++ b/components/Input/styles.scss @@ -2,35 +2,19 @@ position: relative; font-size: 1.1rem; - & > .inputLabel { - position: absolute; - top: 48%; - left: 10px; - transform: translateY(-50%); - padding: 0 4px; - background-color: #d94253; - font-size: 1em; - font-weight: 300; - color: #ffffff; - transition: top 0.3s ease, font-size 0.3s ease; - pointer-events: none; - - &.focused { - top: 0; - font-size: 0.8em; - } - } - & > .input { - padding: 10px 20px; + padding: 7px 14px; width: 100%; - border: 1px solid #ffffff; + border: 1px solid #d94253; border-radius: 5px; - background-color: #d94253; - font-size: 1em; - font-weight: 300; - color: #ffffff; + background-color: #f5cfd3; + font-size: 0.9rem; + color: #d94253; outline: none; + + &::placeholder { + color: #d94253; + } } & > .passwordToggleButton { @@ -45,6 +29,6 @@ height: 100%; border: none; background-color: transparent; - color: #ffffff; + color: #d94253; } } diff --git a/components/Navbar/Navbar.jsx b/components/Navbar/Navbar.jsx deleted file mode 100644 index 7ba18d2..0000000 --- a/components/Navbar/Navbar.jsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import config from './config'; -import './styles.scss'; -import { usePathname } from 'next/navigation'; - -const Navbar = () => { - const pathname = usePathname(); - - return ( - - ); -}; - -export default Navbar; diff --git a/components/Navbar/Navbar.tsx b/components/Navbar/Navbar.tsx new file mode 100644 index 0000000..1a6a32e --- /dev/null +++ b/components/Navbar/Navbar.tsx @@ -0,0 +1,46 @@ +'use client'; + +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// --------------------------------------------------- Components ------------------------------------------------------ +import Link from 'next/link'; +// --------------------------------------------------------------------------------------------------------------------- + +// -------------------------------------------------- Hooks & Utils ---------------------------------------------------- +import { usePathname } from 'next/navigation'; +import config from './config'; +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Assets & Styles --------------------------------------------------- +import './styles.scss'; +// --------------------------------------------------------------------------------------------------------------------- + +const Navbar = () => { + const pathname = usePathname(); + + return ( + + ); +}; + +export default Navbar; diff --git a/components/Navbar/config.js b/components/Navbar/config.ts similarity index 100% rename from components/Navbar/config.js rename to components/Navbar/config.ts diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 2a2e4b3..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "@/*": ["./*"] - } - } -} diff --git a/package-lock.json b/package-lock.json index eb12a1e..2f177b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@capacitor/android": "^6.1.2", "@capacitor/cli": "^6.1.2", "@capacitor/core": "^6.1.2", + "@vercel/postgres": "^0.10.0", + "bcrypt": "^5.1.1", "deck.gl": "^9.0.28", "mapbox-gl": "^3.6.0", "next": "14.2.7", @@ -20,6 +22,8 @@ "react-slot-counter": "^3.0.1" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/react": "18.3.10", "eslint": "^8", "eslint-config-next": "14.2.7", "sass": "^1.77.8" @@ -1369,6 +1373,26 @@ "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==", "license": "ISC" }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", @@ -1487,6 +1511,15 @@ "integrity": "sha512-eJ0nDw8140kJorf8ASyKRC53rI+UG6vPxpsKJiGRD6lXsoKTeKYebeEAXiGDWTvi2AMe6+xngxTqqwm58fL3Fw==", "license": "MIT" }, + "node_modules/@neondatabase/serverless": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.9.5.tgz", + "integrity": "sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==", + "license": "MIT", + "dependencies": { + "@types/pg": "8.11.6" + } + }, "node_modules/@next/env": { "version": "14.2.7", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.7.tgz", @@ -1837,6 +1870,16 @@ "@turf/meta": "^5.1.5" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/brotli": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", @@ -1993,6 +2036,35 @@ "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", "license": "MIT" }, + "node_modules/@types/pg": { + "version": "8.11.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz", + "integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz", + "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -2348,6 +2420,20 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/@vercel/postgres": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.10.0.tgz", + "integrity": "sha512-fSD23DxGND40IzSkXjcFcxr53t3Tiym59Is0jSYIFpG4/0f0KO9SGtcp1sXiebvPaGe7N/tU05cH4yt2S6/IPg==", + "license": "Apache-2.0", + "dependencies": { + "@neondatabase/serverless": "^0.9.3", + "bufferutil": "^4.0.8", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=18.14" + } + }, "node_modules/@webcomponents/shadycss": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.11.2.tgz", @@ -2376,6 +2462,12 @@ "node": ">=16.5.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -2399,6 +2491,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2454,6 +2558,40 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2744,6 +2882,20 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -2782,7 +2934,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2830,6 +2981,19 @@ "node": "*" } }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3047,6 +3211,15 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorbrewer": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.5.6.tgz", @@ -3079,9 +3252,14 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, "node_modules/core-assert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", @@ -3137,6 +3315,13 @@ "license": "MIT", "peer": true }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -3447,6 +3632,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4508,6 +4708,53 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/geojson-vt": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", @@ -4823,6 +5070,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4836,6 +5089,19 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4923,7 +5189,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -5821,6 +6086,30 @@ "integrity": "sha512-VKlnoJRFrB8SdJhlVKvW5vI1gGwcZ+mvChEXcSX6r2xDNc/Q2FD9esfBmGCuPZdrJ1feO+YcVFd2PTk0c137Gw==", "license": "BSD-2-Clause" }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/mapbox-gl": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.6.0.tgz", @@ -5924,7 +6213,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6135,6 +6423,58 @@ } } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6145,11 +6485,23 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6281,11 +6633,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -6391,7 +6748,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6458,6 +6814,48 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "license": "MIT" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -6529,6 +6927,51 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, "node_modules/potpack": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", @@ -6857,7 +7300,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -6874,7 +7316,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7018,6 +7459,12 @@ "node": ">=4.0.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -7723,6 +8170,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -7988,12 +8441,28 @@ "pbf": "^3.2.1" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/wgsl_reflect": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.0.10.tgz", "integrity": "sha512-70yzTaAhLFvasD+awEdHmgqKk3Qm2y6CVUU4OiS1rokB/TGi8CFEweUzDIfYn5pt6qhRdi9uArNykKaJy+CnwQ==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8092,6 +8561,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -8207,9 +8705,29 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", diff --git a/package.json b/package.json index cc17f6f..90006a8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "@capacitor/android": "^6.1.2", "@capacitor/cli": "^6.1.2", "@capacitor/core": "^6.1.2", + "@vercel/postgres": "^0.10.0", + "bcrypt": "^5.1.1", "deck.gl": "^9.0.28", "mapbox-gl": "^3.6.0", "next": "14.2.7", @@ -21,6 +23,8 @@ "react-slot-counter": "^3.0.1" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/react": "18.3.10", "eslint": "^8", "eslint-config-next": "14.2.7", "sass": "^1.77.8" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1d61a95 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./*"] + }, + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}