diff --git a/Pipfile b/Pipfile
index 4d377014ae..1fa0efda95 100644
--- a/Pipfile
+++ b/Pipfile
@@ -20,6 +20,8 @@ typing-extensions = "*"
flask-jwt-extended = "==4.6.0"
wtforms = "==3.1.2"
sqlalchemy = "*"
+flask-mail = "*"
+flask-bcrypt = "*"
[requires]
python_version = "3.13"
diff --git a/Pipfile.lock b/Pipfile.lock
index d9e474e972..9fcfdf8cb3 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ffbfb32d0afa5e4bcaba5c2d08c81381a97abd90f22284d2b76647365df5dc50"
+ "sha256": "34e9dc18f8e20c8abdc412b8fefb2789ba72dd185f691d08d34dcb62e1ab0965"
},
"pipfile-spec": 6,
"requires": {
@@ -24,6 +24,75 @@
"markers": "python_version >= '3.10'",
"version": "==1.17.1"
},
+ "bcrypt": {
+ "hashes": [
+ "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4",
+ "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a",
+ "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464",
+ "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4",
+ "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746",
+ "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2",
+ "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41",
+ "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd",
+ "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9",
+ "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e",
+ "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538",
+ "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10",
+ "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb",
+ "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef",
+ "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4",
+ "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23",
+ "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef",
+ "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75",
+ "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42",
+ "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a",
+ "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172",
+ "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683",
+ "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2",
+ "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4",
+ "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba",
+ "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da",
+ "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493",
+ "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254",
+ "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534",
+ "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f",
+ "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c",
+ "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c",
+ "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83",
+ "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff",
+ "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d",
+ "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861",
+ "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5",
+ "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9",
+ "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b",
+ "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac",
+ "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e",
+ "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f",
+ "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb",
+ "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86",
+ "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980",
+ "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd",
+ "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d",
+ "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1",
+ "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911",
+ "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993",
+ "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191",
+ "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4",
+ "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2",
+ "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8",
+ "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db",
+ "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927",
+ "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be",
+ "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb",
+ "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e",
+ "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf",
+ "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd",
+ "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822",
+ "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==5.0.0"
+ },
"blinker": {
"hashes": [
"sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf",
@@ -74,6 +143,14 @@
"markers": "python_version >= '3.10'",
"version": "==2.0.0"
},
+ "flask-bcrypt": {
+ "hashes": [
+ "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a",
+ "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369"
+ ],
+ "index": "pypi",
+ "version": "==1.0.1"
+ },
"flask-cors": {
"hashes": [
"sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c",
@@ -92,6 +169,15 @@
"markers": "python_version >= '3.7' and python_version < '4'",
"version": "==4.6.0"
},
+ "flask-mail": {
+ "hashes": [
+ "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d",
+ "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==0.10.0"
+ },
"flask-migrate": {
"hashes": [
"sha256:1a336b06eb2c3ace005f5f2ded8641d534c18798d64061f6ff11f79e1434126d",
@@ -121,6 +207,8 @@
"greenlet": {
"hashes": [
"sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b",
+ "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681",
+ "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5",
"sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735",
"sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079",
"sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d",
@@ -133,18 +221,23 @@
"sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671",
"sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8",
"sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d",
+ "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10",
+ "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269",
"sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f",
+ "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d",
"sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0",
"sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd",
"sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337",
"sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0",
"sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633",
+ "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be",
"sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b",
"sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa",
"sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31",
"sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9",
"sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b",
"sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4",
+ "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b",
"sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc",
"sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c",
"sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98",
@@ -157,6 +250,7 @@
"sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5",
"sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02",
"sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0",
+ "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8",
"sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1",
"sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c",
"sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594",
@@ -166,14 +260,18 @@
"sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6",
"sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b",
"sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df",
+ "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c",
+ "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929",
"sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945",
"sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae",
"sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb",
"sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504",
"sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb",
"sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01",
+ "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0",
"sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c",
- "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"
+ "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968",
+ "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7"
],
"markers": "python_version >= '3.9'",
"version": "==3.2.4"
diff --git a/index.html b/index.html
index 27a99f796e..3531854c7e 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,7 @@
-
Hello Rigo
+ MeetFit
diff --git a/package-lock.json b/package-lock.json
index 8d43d98ab7..5702bda808 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,14 @@
"version": "1.0.1",
"license": "ISC",
"dependencies": {
+ "@react-google-maps/api": "^2.20.7",
+ "bootstrap": "^5.3.8",
+ "bootstrap-icons": "^1.13.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
+ "react-bootstrap": "^2.10.10",
"react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.18.0"
},
"devDependencies": {
@@ -73,6 +78,7 @@
"integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -290,6 +296,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
@@ -804,6 +819,22 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@googlemaps/js-api-loader": {
+ "version": "1.16.8",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
+ "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -884,7 +915,6 @@
"dev": true,
"license": "MIT",
"optional": true,
- "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -943,6 +973,62 @@
"node": ">= 8"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
+ "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-google-maps/api": {
+ "version": "2.20.7",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.7.tgz",
+ "integrity": "sha512-ys7uri3V6gjhYZUI43srHzSKDC6/jiKTwHNlwXFTvjeaJE3M3OaYBt9FZKvJs8qnOhL6i6nD1BKJoi1KrnkCkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@googlemaps/js-api-loader": "1.16.8",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.20.0",
+ "@react-google-maps/marker-clusterer": "2.20.0",
+ "@types/google.maps": "3.58.1",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/@react-google-maps/infobox": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
+ "integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ==",
+ "license": "MIT"
+ },
+ "node_modules/@react-google-maps/marker-clusterer": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
+ "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==",
+ "license": "MIT"
+ },
"node_modules/@remix-run/router": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
@@ -952,6 +1038,69 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
+ "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@popperjs/core": "^2.11.8",
+ "@react-aria/ssr": "^3.5.0",
+ "@restart/hooks": "^0.5.0",
+ "@types/warning": "^3.0.3",
+ "dequal": "^2.0.3",
+ "dom-helpers": "^5.2.0",
+ "uncontrollable": "^8.0.4",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/@restart/hooks": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
+ "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/uncontrollable": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
+ "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.14.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -997,27 +1146,24 @@
"@babel/types": "^7.20.7"
}
},
- "node_modules/@types/node": {
- "version": "16.11.12",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
- "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==",
- "dev": true,
- "optional": true,
- "peer": true
+ "node_modules/@types/google.maps": {
+ "version": "3.58.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
+ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==",
+ "license": "MIT"
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -1033,6 +1179,21 @@
"@types/react": "^18.0.0"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
+ "license": "MIT"
+ },
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@@ -1066,6 +1227,7 @@
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1264,6 +1426,41 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/bootstrap": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
+ "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
+ "node_modules/bootstrap-icons": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
+ "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1294,6 +1491,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -1312,8 +1510,7 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
- "optional": true,
- "peer": true
+ "optional": true
},
"node_modules/call-bind": {
"version": "1.0.8",
@@ -1395,6 +1592,12 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1420,7 +1623,6 @@
"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/data-view-buffer": {
@@ -1537,6 +1739,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -1549,6 +1760,16 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1797,6 +2018,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -2215,8 +2437,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
@@ -2630,6 +2851,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -3099,6 +3329,12 @@
"node": ">=4.0"
}
},
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==",
+ "license": "ISC"
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3452,6 +3688,19 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=0.14.0"
+ }
+ },
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -3486,6 +3735,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -3493,11 +3743,43 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-bootstrap": {
+ "version": "2.10.10",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
+ "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@restart/hooks": "^0.4.9",
+ "@restart/ui": "^1.9.4",
+ "@types/prop-types": "^15.7.12",
+ "@types/react-transition-group": "^4.4.6",
+ "classnames": "^2.3.2",
+ "dom-helpers": "^5.2.1",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.8.1",
+ "prop-types-extra": "^1.1.0",
+ "react-transition-group": "^4.4.5",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.8",
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -3506,11 +3788,26 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
+ "license": "MIT"
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -3553,6 +3850,22 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -3921,7 +4234,6 @@
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"optional": true,
- "peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -3933,7 +4245,6 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4061,6 +4372,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "license": "ISC",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -4081,7 +4401,6 @@
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
- "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -4100,8 +4419,7 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
- "optional": true,
- "peer": true
+ "optional": true
},
"node_modules/text-table": {
"version": "0.2.0",
@@ -4109,6 +4427,12 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4232,6 +4556,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
@@ -4278,6 +4617,7 @@
"integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
@@ -4328,6 +4668,15 @@
}
}
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4500,6 +4849,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
"integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
"dev": true,
+ "peer": true,
"requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -4651,6 +5001,11 @@
"@babel/helper-plugin-utils": "^7.25.9"
}
},
+ "@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
+ },
"@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
@@ -4898,6 +5253,20 @@
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"dev": true
},
+ "@googlemaps/js-api-loader": {
+ "version": "1.16.8",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
+ "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ=="
+ },
+ "@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "requires": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -4950,7 +5319,6 @@
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dev": true,
"optional": true,
- "peer": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -4998,11 +5366,96 @@
"fastq": "^1.6.0"
}
},
+ "@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "peer": true
+ },
+ "@react-aria/ssr": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
+ "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
+ "requires": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "@react-google-maps/api": {
+ "version": "2.20.7",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.7.tgz",
+ "integrity": "sha512-ys7uri3V6gjhYZUI43srHzSKDC6/jiKTwHNlwXFTvjeaJE3M3OaYBt9FZKvJs8qnOhL6i6nD1BKJoi1KrnkCkg==",
+ "requires": {
+ "@googlemaps/js-api-loader": "1.16.8",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.20.0",
+ "@react-google-maps/marker-clusterer": "2.20.0",
+ "@types/google.maps": "3.58.1",
+ "invariant": "2.2.4"
+ }
+ },
+ "@react-google-maps/infobox": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
+ "integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ=="
+ },
+ "@react-google-maps/marker-clusterer": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
+ "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw=="
+ },
"@remix-run/router": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
"integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw=="
},
+ "@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "requires": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "@restart/ui": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
+ "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
+ "requires": {
+ "@babel/runtime": "^7.26.0",
+ "@popperjs/core": "^2.11.8",
+ "@react-aria/ssr": "^3.5.0",
+ "@restart/hooks": "^0.5.0",
+ "@types/warning": "^3.0.3",
+ "dequal": "^2.0.3",
+ "dom-helpers": "^5.2.0",
+ "uncontrollable": "^8.0.4",
+ "warning": "^4.0.3"
+ },
+ "dependencies": {
+ "@restart/hooks": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
+ "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
+ "requires": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "uncontrollable": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
+ "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
+ "requires": {}
+ }
+ }
+ },
+ "@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "requires": {
+ "tslib": "^2.8.0"
+ }
+ },
"@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -5044,25 +5497,21 @@
"@babel/types": "^7.20.7"
}
},
- "@types/node": {
- "version": "16.11.12",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
- "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==",
- "dev": true,
- "optional": true,
- "peer": true
+ "@types/google.maps": {
+ "version": "3.58.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
+ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ=="
},
"@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
- "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
- "dev": true
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
},
"@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "dev": true,
+ "peer": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -5075,6 +5524,17 @@
"dev": true,
"requires": {}
},
+ "@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "requires": {}
+ },
+ "@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q=="
+ },
"@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@@ -5098,7 +5558,8 @@
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"acorn-jsx": {
"version": "5.3.2",
@@ -5230,6 +5691,17 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "bootstrap": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
+ "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
+ "requires": {}
+ },
+ "bootstrap-icons": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
+ "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw=="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5245,6 +5717,7 @@
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
"dev": true,
+ "peer": true,
"requires": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -5257,8 +5730,7 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
- "optional": true,
- "peer": true
+ "optional": true
},
"call-bind": {
"version": "1.0.8",
@@ -5304,6 +5776,11 @@
"integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==",
"dev": true
},
+ "classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -5324,8 +5801,7 @@
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"data-view-buffer": {
"version": "1.0.2",
@@ -5397,6 +5873,11 @@
"object-keys": "^1.1.1"
}
},
+ "dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
+ },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -5406,6 +5887,15 @@
"esutils": "^2.0.2"
}
},
+ "dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -5600,6 +6090,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"dev": true,
+ "peer": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -5883,8 +6374,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
@@ -6164,6 +6654,14 @@
"side-channel": "^1.1.0"
}
},
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -6451,6 +6949,11 @@
"object.assign": "^4.1.2"
}
},
+ "kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -6686,6 +7189,15 @@
"react-is": "^16.13.1"
}
},
+ "prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "requires": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ }
+ },
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -6702,24 +7214,57 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "peer": true,
"requires": {
"loose-envify": "^1.1.0"
}
},
+ "react-bootstrap": {
+ "version": "2.10.10",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
+ "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
+ "requires": {
+ "@babel/runtime": "^7.24.7",
+ "@restart/hooks": "^0.4.9",
+ "@restart/ui": "^1.9.4",
+ "@types/prop-types": "^15.7.12",
+ "@types/react-transition-group": "^4.4.6",
+ "classnames": "^2.3.2",
+ "dom-helpers": "^5.2.1",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.8.1",
+ "prop-types-extra": "^1.1.0",
+ "react-transition-group": "^4.4.5",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ }
+ },
"react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "peer": true,
"requires": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
}
},
+ "react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "requires": {}
+ },
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -6743,6 +7288,17 @@
"react-router": "6.29.0"
}
},
+ "react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
"reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6988,7 +7544,6 @@
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"optional": true,
- "peer": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -6999,8 +7554,7 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
- "optional": true,
- "peer": true
+ "optional": true
}
}
},
@@ -7088,6 +7642,14 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
+ "supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "requires": {
+ "kdbush": "^4.0.2"
+ }
+ },
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -7100,7 +7662,6 @@
"integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==",
"dev": true,
"optional": true,
- "peer": true,
"requires": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -7113,8 +7674,7 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
- "optional": true,
- "peer": true
+ "optional": true
}
}
},
@@ -7124,6 +7684,11 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
+ "tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -7204,6 +7769,17 @@
"which-boxed-primitive": "^1.1.1"
}
},
+ "uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "requires": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"update-browserslist-db": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
@@ -7228,6 +7804,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
"integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
"dev": true,
+ "peer": true,
"requires": {
"esbuild": "^0.18.10",
"fsevents": "~2.3.2",
@@ -7235,6 +7812,14 @@
"rollup": "^3.27.1"
}
},
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 0caab10749..359839d920 100755
--- a/package.json
+++ b/package.json
@@ -8,10 +8,10 @@
"main": "index.js",
"scripts": {
"dev": "vite",
- "start": "vite",
- "build": "vite build",
- "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
- "preview": "vite preview"
+ "start": "vite",
+ "build": "vite build",
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
},
"author": {
"name": "Alejandro Sanchez",
@@ -30,13 +30,13 @@
"license": "ISC",
"devDependencies": {
"@types/react": "^18.2.18",
- "@types/react-dom": "^18.2.7",
- "@vitejs/plugin-react": "^4.0.4",
- "eslint": "^8.46.0",
- "eslint-plugin-react": "^7.33.1",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.3",
- "vite": "^4.4.8"
+ "@types/react-dom": "^18.2.7",
+ "@vitejs/plugin-react": "^4.0.4",
+ "eslint": "^8.46.0",
+ "eslint-plugin-react": "^7.33.1",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "vite": "^4.4.8"
},
"babel": {
"presets": [
@@ -54,9 +54,14 @@
]
},
"dependencies": {
+ "@react-google-maps/api": "^2.20.7",
+ "bootstrap": "^5.3.8",
+ "bootstrap-icons": "^1.13.1",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-router-dom": "^6.18.0"
+ "react": "^18.2.0",
+ "react-bootstrap": "^2.10.10",
+ "react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^6.18.0"
}
}
diff --git a/src/api/models.py b/src/api/models.py
index da515f6a1a..68eac0dba0 100644
--- a/src/api/models.py
+++ b/src/api/models.py
@@ -1,19 +1,157 @@
+from datetime import datetime, timezone, timedelta
+import secrets
from flask_sqlalchemy import SQLAlchemy
-from sqlalchemy import String, Boolean
-from sqlalchemy.orm import Mapped, mapped_column
+from sqlalchemy import (String, Float, DateTime, Text, Date, Time, Integer, ForeignKey, Table, Boolean)
+from sqlalchemy.orm import mapped_column, relationship
+from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
+# Tabla intermedia (relación muchos a muchos)
+activity_user = Table(
+ "activity_user",
+ db.Model.metadata,
+ db.Column("id", Integer, primary_key=True),
+ db.Column("user_id", Integer, ForeignKey("user.id")),
+ db.Column("activity_id", Integer, ForeignKey("activity.id")),
+ db.Column("joined_at", DateTime, default=lambda: datetime.now(timezone.utc))
+)
+
+# MODELO: User
class User(db.Model):
- id: Mapped[int] = mapped_column(primary_key=True)
- email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False)
- password: Mapped[str] = mapped_column(nullable=False)
- is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False)
+ __tablename__ = "user"
+
+ id = mapped_column(Integer, primary_key=True)
+ name = mapped_column(String(50), nullable=False)
+ email = mapped_column(String(120), unique=True, nullable=False)
+ password_hash = mapped_column(String(200), nullable=False)
+ avatar_url = mapped_column(String(200))
+ biography = mapped_column(Text)
+ sports = mapped_column(String(200))
+ level = mapped_column(String(20))
+ latitude = mapped_column(Float)
+ longitude = mapped_column(Float)
+ created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
+
+ # Relaciones
+ activities_created = relationship("Activity", back_populates="creator", lazy=True)
+ activities_joined = relationship("Activity", secondary=activity_user, back_populates="participants")
+ messages_sent = relationship("Message", back_populates="sender", lazy=True)
+ reset_tokens = relationship("PasswordResetToken", back_populates="user", cascade="all, delete-orphan")
+
+ # Métodos de seguridad
+ def set_password(self, password):
+ self.password_hash = generate_password_hash(password)
+ def check_password(self, password):
+ return check_password_hash(self.password_hash, password)
def serialize(self):
return {
"id": self.id,
+ "name": self.name,
"email": self.email,
- # do not serialize the password, its a security breach
- }
\ No newline at end of file
+ "avatar_url": self.avatar_url,
+ "biography": self.biography,
+ "sports": self.sports,
+ "level": self.level,
+ "latitude": self.latitude,
+ "longitude": self.longitude,
+ "created_at": self.created_at.isoformat(),
+ }
+
+
+# MODELO: Activity
+class Activity(db.Model):
+ __tablename__ = "activity"
+
+ id = mapped_column(Integer, primary_key=True)
+ title = mapped_column(String(100), nullable=False)
+ sport = mapped_column(String(50), nullable=False)
+ description = mapped_column(Text)
+ latitude = mapped_column(Float, nullable=False)
+ longitude = mapped_column(Float, nullable=False)
+ date = mapped_column(Date, nullable=False)
+ time = mapped_column(Time, nullable=False)
+ created_by = mapped_column(Integer, ForeignKey("user.id"))
+ max_participants = mapped_column(Integer)
+ created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
+
+ # Relaciones
+ creator = relationship("User", back_populates="activities_created")
+ participants = relationship("User", secondary=activity_user, back_populates="activities_joined")
+ messages = relationship("Message", back_populates="activity", lazy=True)
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "title": self.title,
+ "sport": self.sport,
+ "description": self.description,
+ "latitude": self.latitude,
+ "longitude": self.longitude,
+ "date": str(self.date),
+ "time": str(self.time),
+ "created_by": self.created_by,
+ "creator_name": self.creator.name if self.creator else None,
+ "max_participants": self.max_participants,
+ "created_at": self.created_at.isoformat(),
+ "participants": [p.id for p in self.participants],
+ }
+
+
+# MODELO: Message
+class Message(db.Model):
+ __tablename__ = "message"
+
+ id = mapped_column(Integer, primary_key=True)
+ activity_id = mapped_column(Integer, ForeignKey("activity.id"))
+ sender_id = mapped_column(Integer, ForeignKey("user.id"))
+ message = mapped_column(Text, nullable=False)
+ created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
+
+ sender = relationship("User", back_populates="messages_sent")
+ activity = relationship("Activity", back_populates="messages")
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "activity_id": self.activity_id,
+ "sender_id": self.sender_id,
+ "message": self.message,
+ "created_at": self.created_at.isoformat(),
+ }
+
+# MODELO: PasswordResetToken
+class PasswordResetToken(db.Model):
+ __tablename__ = "password_reset_token"
+
+ id = mapped_column(Integer, primary_key=True)
+ user_id = mapped_column(Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False)
+ token = mapped_column(String(128), unique=True, nullable=False)
+ expires_at = mapped_column(DateTime(timezone=True), nullable=False)
+ used = mapped_column(Boolean, default=False)
+ created_at = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
+
+ user = relationship("User", back_populates="reset_tokens")
+
+ @staticmethod
+ def generate_token(user_id, expiration_minutes=30):
+ """Genera un nuevo token de recuperación con validez temporal."""
+ token = secrets.token_urlsafe(64)
+ expires_at = datetime.now(timezone.utc) + timedelta(minutes=expiration_minutes)
+ return PasswordResetToken(user_id=user_id, token=token, expires_at=expires_at)
+
+ def is_valid(self):
+ """Valida si el token sigue activo y no se ha usado."""
+ return not self.used and datetime.now(timezone.utc) < self.expires_at
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "user_id": self.user_id,
+ "token": self.token,
+ "expires_at": self.expires_at.isoformat(),
+ "used": self.used,
+ "created_at": self.created_at.isoformat(),
+ }
diff --git a/src/app.py b/src/app.py
index 1b3340c0fa..6724370ab2 100644
--- a/src/app.py
+++ b/src/app.py
@@ -6,11 +6,20 @@
from flask_migrate import Migrate
from flask_swagger import swagger
from api.utils import APIException, generate_sitemap
-from api.models import db
+from api.models import db, User, Activity, Message, PasswordResetToken
from api.routes import api
from api.admin import setup_admin
from api.commands import setup_commands
+from flask_jwt_extended import create_access_token
+from flask_jwt_extended import get_jwt_identity
+from flask_jwt_extended import jwt_required
+from flask_jwt_extended import JWTManager
+
+from flask_bcrypt import Bcrypt
+
+from flask_mail import Mail, Message
+
# from models import Person
ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production"
@@ -19,6 +28,27 @@
app = Flask(__name__)
app.url_map.strict_slashes = False
+# Setup the Flask-JWT-Extended extension
+app.config["JWT_SECRET_KEY"] = "ESTA_ES_NUESTRA_LLAVE"
+
+GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY")
+
+jwt = JWTManager(app)
+
+bcrypt = Bcrypt(app)
+
+app.config.update(dict(
+ DEBUG=False,
+ MAIL_SERVER='smtp.gmail.com',
+ MAIL_PORT=587,
+ MAIL_USE_TLS=True,
+ MAIL_USE_SSL=False,
+ MAIL_USERNAME='meetfitfspt119@gmail.com',
+ MAIL_PASSWORD=os.getenv('MAIL_PASSWORD')
+))
+
+mail = Mail(app)
+
# database condiguration
db_url = os.getenv("DATABASE_URL")
if db_url is not None:
@@ -66,6 +96,92 @@ def serve_any_other_file(path):
return response
+#prueba send-mail
+@app.route('/api/send-mail', methods=['GET'])
+def send_mail():
+ msg = Message(
+ subject='Prueba de correo de proyecto',
+ sender='meetfitfspt119@gmail.com',
+ recipients=['meetfitfspt119@gmail.com'],
+ )
+ msg.html = 'Testeando envio de correo '
+ mail.send(msg)
+ return jsonify({'msg': 'Correo enviado con exito'}), 200
+
+@app.route('/api/register', methods=['POST'])
+def register():
+ body = request.get_json(silent=True)
+
+ if body is None:
+ return jsonify({'msg': 'POST method needs a body or email/password not found.'}), 400
+ if 'email' not in body:
+ return jsonify({'msg': 'email field is mandatory'}), 400
+ if 'password' not in body:
+ return jsonify({'msg': 'password field is mandatory'}), 400
+ if 'name' not in body:
+ return jsonify({'msg': 'name field is mandatory'})
+ email = body.get("email")
+ password = body.get("password")
+ name = body.get("name")
+
+ user = User.query.filter_by(email=email).first()
+ if user is not None:
+ return jsonify({'msg': 'Usuario ya registrado!'}), 400
+
+ pw_hash = bcrypt.generate_password_hash(password).decode('utf-8')
+ user = User(email=email, name=name, password_hash=pw_hash)
+ db.session.add(user)
+ db.session.commit()
+
+
+ # access_token = create_access_token(identity=str(user.id))
+ return jsonify({'msg': 'User Registered successfully'})
+
+@app.route('/api/login', methods=['POST'])
+def login():
+
+ body = request.get_json(silent=True)
+ # validar body
+ if body is None:
+ return jsonify({'msg': 'POST method needs a body or email/password not found.'}), 400
+ if 'email' not in body:
+ return jsonify({'msg': 'email field is mandatory'}), 400
+ if 'password' not in body:
+ return jsonify({'msg': 'password field is mandatory'}), 400
+ # buscar usuario
+ email = body['email']
+ password = body['password']
+
+ user = User.query.filter_by(email=email).first()
+ if not user:
+ return jsonify({'msg':'User or password incorrect'}), 400
+ # validar password
+ is_password = bcrypt.check_password_hash(user.password_hash, password)
+
+ if not is_password:
+ return jsonify({'msg': 'User or password incorrect'}), 400
+ # crear token
+ access_token = create_access_token(identity=str(user.id))
+
+ return jsonify(access_token=access_token)
+
+
+@app.route("/api/me", methods=["GET"])
+@jwt_required()
+def me():
+
+
+ current_user = get_jwt_identity()
+ user = User.query.get(current_user)
+ print(user)
+ return jsonify(user.serialize()), 200
+
+
+#endPoint completo ACTIVITY ----> GET POST PUT DELETE
+
+
+
+
# this only runs if `$ python src/main.py` is executed
if __name__ == '__main__':
PORT = int(os.environ.get('PORT', 3001))
diff --git a/src/front/components/AuthNotice.jsx b/src/front/components/AuthNotice.jsx
new file mode 100644
index 0000000000..426c636e95
--- /dev/null
+++ b/src/front/components/AuthNotice.jsx
@@ -0,0 +1,15 @@
+// Lo he creado para mostrar un aviso genérico relacionado con la autenticación
+export function AuthNotice({ icon = "📩", title = "Mira tu email", text = "te hemos enviado las instrucciones a tu email" }) {
+ return (
+
+ );
+}
diff --git a/src/front/components/AuthShell.jsx b/src/front/components/AuthShell.jsx
new file mode 100644
index 0000000000..2354e011e3
--- /dev/null
+++ b/src/front/components/AuthShell.jsx
@@ -0,0 +1,14 @@
+// Lo he creado para envolver las pantallas de autenticación con un diseño útil
+export default function AuthShell({ children, title, subtitle }) {
+ return (
+
+
+
+ {title &&
{title}
}
+ {subtitle &&
{subtitle}
}
+ {children}
+
+
+
+ );
+}
diff --git a/src/front/components/EditProfile.jsx b/src/front/components/EditProfile.jsx
new file mode 100644
index 0000000000..5096ecfb0c
--- /dev/null
+++ b/src/front/components/EditProfile.jsx
@@ -0,0 +1,140 @@
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Form, Button, Container, Card, Spinner } from "react-bootstrap";
+
+export const EditProfile = () => {
+ const [formData, setFormData] = useState({
+ name: "",
+ biography: "",
+ sports: "",
+ level: "",
+ avatar_url: "",
+ latitude: "",
+ longitude: ""
+ });
+ const [loading, setLoading] = useState(true);
+ const navigate = useNavigate();
+
+ const token = localStorage.getItem("token");
+ const userId = localStorage.getItem("user_id");
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ if (!resp.ok) throw new Error("Error fetching user");
+ const data = await resp.json();
+ setFormData(data);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+ if (userId) fetchUser();
+ }, [userId, token]);
+
+ const handleChange = (e) =>
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ try {
+ const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify(formData),
+ });
+ if (!resp.ok) throw new Error("Error updating profile");
+ navigate("/profile");
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ if (loading)
+ return (
+
+
+
+ );
+
+ return (
+
+
+ Edit Profile
+
+
+ Name
+
+
+
+
+ Biography
+
+
+
+
+ Sports
+
+
+
+
+ Level
+
+
+
+
+ Avatar URL
+
+
+
+
+ navigate("/profile")}>
+ Cancel
+
+
+ Save Changes
+
+
+
+
+
+ );
+};
diff --git a/src/front/components/ProfileView.jsx b/src/front/components/ProfileView.jsx
new file mode 100644
index 0000000000..8b4d622e3a
--- /dev/null
+++ b/src/front/components/ProfileView.jsx
@@ -0,0 +1,69 @@
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Card, Button, Container, Row, Col, Spinner } from "react-bootstrap";
+
+export const ProfileView = () => {
+ const [user, setUser] = useState(null);
+ const navigate = useNavigate();
+
+ const token = localStorage.getItem("token");
+ const userId = localStorage.getItem("user_id"); // guarda este valor al hacer login
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ if (!resp.ok) throw new Error("Error fetching user");
+ const data = await resp.json();
+ setUser(data);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+ if (userId) fetchUser();
+ }, [userId, token]);
+
+ if (!user)
+ return (
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ {user.name}
+ {user.email}
+ {user.biography || "No biography yet"}
+
+
+
+
Sports: {user.sports || "Not specified"}
+
Level: {user.level || "Not specified"}
+
+
+ navigate("/edit-profile")}
+ >
+ Edit Profile
+
+
+
+ );
+};
diff --git a/src/front/components/TextInput.jsx b/src/front/components/TextInput.jsx
new file mode 100644
index 0000000000..c4561012d9
--- /dev/null
+++ b/src/front/components/TextInput.jsx
@@ -0,0 +1,43 @@
+//Input que permite texto y opcionalmente toggle para mostrar/ocultar (para passwords)
+
+import { useState } from "react";
+
+export default function TextInput({
+ label,
+ type = "text",
+ name,
+ value,
+ onChange,
+ placeholder,
+ withToggle = false,
+ required = true,
+}) {
+ const [show, setShow] = useState(false);
+ const finalType = withToggle ? (show ? "text" : "password") : type;
+
+ return (
+
+ {label && {label} }
+
+ {withToggle && (
+ setShow((s) => !s)}
+ aria-label="toggle password"
+ >
+ {show ? "🙈" : "👁️"}
+
+ )}
+
+ );
+}
diff --git a/src/front/index.css b/src/front/index.css
index e69de29bb2..b13c814e6f 100644
--- a/src/front/index.css
+++ b/src/front/index.css
@@ -0,0 +1,128 @@
+/* Tema de autenticación oscuro */
+.auth-theme {
+
+ --bg: #0f1b2b;
+ --card: #1a2740;
+ --muted: #a3b1c6;
+ --text: #e8eef7;
+ --accent: #ff6b4a;
+ --accent-hover: #ff5832;
+ --input: #0e1a2a;
+ --radius: 22px;
+
+
+ background: #f7eded;
+ min-height: 100vh;
+}
+
+/* Layout y tarjeta */
+.auth-theme .auth-shell {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f7eded;
+}
+
+.auth-theme .auth-card {
+ width: 360px;
+ max-width: 92vw;
+ background: var(--bg);
+ border-radius: var(--radius);
+ padding: 28px 22px;
+ color: var(--text);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
+}
+
+.auth-theme .auth-title {
+ font-size: 22px;
+ font-weight: 700;
+ text-align: center;
+ margin: 6px 0 18px;
+}
+.auth-theme .auth-sub {
+ color: var(--muted);
+ font-size: 13px;
+ text-align: center;
+ margin-bottom: 12px;
+}
+
+/* Inputs y labels dentro del scope */
+.auth-theme .form-label {
+ color: var(--muted);
+ font-size: 13px;
+ margin-bottom: 6px;
+}
+.auth-theme .input-dark.form-control {
+ background: var(--input);
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ color: var(--text);
+ border-radius: 12px;
+ height: 44px;
+}
+.auth-theme .input-dark.form-control::placeholder {
+ color: #7f8ba1;
+}
+.auth-theme .input-dark.form-control:focus {
+ background: var(--input);
+ color: var(--text);
+ border-color: rgba(255, 255, 255, 0.15);
+ box-shadow: 0 0 0 3px rgba(255, 107, 74, 0.15);
+}
+
+/* Botones y links (solo auth) */
+.auth-theme .btn-accent {
+ background: var(--accent);
+ border: none;
+ color: #fff;
+ height: 46px;
+ border-radius: 12px;
+ font-weight: 600;
+}
+.auth-theme .btn-accent:hover {
+ background: var(--accent-hover);
+}
+
+.auth-theme .link-muted {
+ color: var(--muted);
+ font-size: 13px;
+}
+.auth-theme .link-accent {
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 600;
+}
+.auth-theme .link-accent:hover {
+ color: var(--accent-hover);
+}
+
+/* Icono ojo y notas */
+.auth-theme .eye-btn {
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: transparent;
+ border: 0;
+ color: #9ab0cc;
+}
+.auth-theme .small-note {
+ font-size: 12px;
+ color: var(--muted);
+ margin-top: 6px;
+}
+
+/* Select oscuro */
+.auth-theme .select-dark.form-select {
+ background: var(--input);
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ color: var(--text);
+ border-radius: 12px;
+ height: 44px;
+}
+.auth-theme .select-dark.form-select:focus {
+ background: var(--input);
+ color: var(--text);
+ border-color: rgba(255, 255, 255, 0.15);
+ box-shadow: 0 0 0 3px rgba(255, 107, 74, 0.15);
+}
diff --git a/src/front/main.jsx b/src/front/main.jsx
index a5a3c781dc..74f4dbc99b 100644
--- a/src/front/main.jsx
+++ b/src/front/main.jsx
@@ -1,29 +1,29 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import './index.css' // Global styles for your application
-import { RouterProvider } from "react-router-dom"; // Import RouterProvider to use the router
-import { router } from "./routes"; // Import the router configuration
-import { StoreProvider } from './hooks/useGlobalReducer'; // Import the StoreProvider for global state management
-import { BackendURL } from './components/BackendURL';
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "./index.css";
+import { RouterProvider } from "react-router-dom";
+import { router } from "./routes";
+import { StoreProvider } from "./hooks/useGlobalReducer";
+import { BackendURL } from "./components/BackendURL";
const Main = () => {
-
- if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return (
-
-
-
+ const backendURL = import.meta.env.VITE_BACKEND_URL;
+
+ if ( ! backendURL || backendURL === "") {
+ return (
+
+
+
);
+ }
+
return (
-
- {/* Provide global state to all components */}
-
- {/* Set up routing for the application */}
-
-
+
+
+
);
-}
+};
-// Render the Main component into the root DOM element.
-ReactDOM.createRoot(document.getElementById('root')).render( )
+ReactDOM.createRoot(document.getElementById("root")).render( );
diff --git a/src/front/pages/Forgot.jsx b/src/front/pages/Forgot.jsx
new file mode 100644
index 0000000000..d00b1fa2e0
--- /dev/null
+++ b/src/front/pages/Forgot.jsx
@@ -0,0 +1,36 @@
+
+// Pide email y muestra confirmación (solo UI).
+
+import { useState } from "react";
+import AuthShell from "../components/AuthShell";
+import TextInput from "../components/TextInput";
+
+export function Forgot() {
+ const [email, setEmail] = useState("");
+ const [sent, setSent] = useState(false);
+ const [err, setErr] = useState("");
+
+ const submit = (e) => {
+ e.preventDefault();
+ setErr("");
+ if (!email) return setErr("Introduce tu email.");
+ console.log("Forgot (solo UI):", email);
+ setSent(true);
+ };
+
+ return (
+
+ {!sent ? (
+
+ ) : (
+
+ Revisa tu correo — te hemos enviado instrucciones de recuperación.
+
+ )}
+
+ );
+}
diff --git a/src/front/pages/Home.jsx b/src/front/pages/Home.jsx
index 341ed21768..be0b6084fb 100644
--- a/src/front/pages/Home.jsx
+++ b/src/front/pages/Home.jsx
@@ -1,52 +1,45 @@
-import React, { useEffect } from "react"
-import rigoImageUrl from "../assets/img/rigo-baby.jpg";
-import useGlobalReducer from "../hooks/useGlobalReducer.jsx";
+import React from "react";
+import { useNavigate } from "react-router-dom";
+import { Container, Button } from "react-bootstrap";
export const Home = () => {
-
- const { store, dispatch } = useGlobalReducer()
-
- const loadMessage = async () => {
- try {
- const backendUrl = import.meta.env.VITE_BACKEND_URL
-
- if (!backendUrl) throw new Error("VITE_BACKEND_URL is not defined in .env file")
-
- const response = await fetch(backendUrl + "/api/hello")
- const data = await response.json()
-
- if (response.ok) dispatch({ type: "set_hello", payload: data.message })
-
- return data
-
- } catch (error) {
- if (error.message) throw new Error(
- `Could not fetch the message from the backend.
- Please check if the backend is running and the backend port is public.`
- );
- }
-
- }
-
- useEffect(() => {
- loadMessage()
- }, [])
-
- return (
-
-
Hello Rigo!!
-
-
-
-
- {store.message ? (
- {store.message}
- ) : (
-
- Loading message from the backend (make sure your python 🐍 backend is running)...
-
- )}
-
-
- );
-};
\ No newline at end of file
+ const navigate = useNavigate();
+
+ return (
+
+ {/* Logo */}
+
+
+
+
+ {/* Título */}
+
+ MeetFit
+
+
+ {/* Botón de Login */}
+
+ navigate("/login")}
+ >
+ Login
+
+
+
+ );
+};
diff --git a/src/front/pages/Login.jsx b/src/front/pages/Login.jsx
new file mode 100644
index 0000000000..3e342f4b63
--- /dev/null
+++ b/src/front/pages/Login.jsx
@@ -0,0 +1,70 @@
+
+// Guarda email y password en el estado local y simula login en la consola.
+// Muestra el aviso de "Cuenta creada" si viene el query param registered=1.
+// Usa AuthShell y TextInput como componentes hijos.
+
+import { useState, useEffect } from "react";
+import { Link, useNavigate, useSearchParams } from "react-router-dom";
+import AuthShell from "../components/AuthShell";
+import TextInput from "../components/TextInput";
+
+export function Login() {
+ const nav = useNavigate();
+ const [sp] = useSearchParams();
+
+ const [form, setForm] = useState({ email: "", password: "" });
+ const [err, setErr] = useState("");
+ const [ok, setOk] = useState("");
+
+ useEffect(() => {
+ if (sp.get("registered")) setOk("Cuenta creada. Ya puedes iniciar sesión.");
+ }, [sp]);
+
+ const onChange = (e) =>
+ setForm((f) => ({ ...f, [e.target.name]: e.target.value }));
+
+ const submit = (e) => {
+ e.preventDefault();
+ setErr("");
+ if (!form.email || !form.password) {
+ setErr("Completa el email y la contraseña.");
+ return;
+ }
+ console.log("Login (solo UI):", form);
+ nav("/login");
+ };
+
+ return (
+
+ {ok && {ok}
}
+
+
+ ¿No tienes cuenta?
+
+ Regístrate
+
+
+
+ );
+}
diff --git a/src/front/pages/MapView.jsx b/src/front/pages/MapView.jsx
new file mode 100644
index 0000000000..ef4abeca8f
--- /dev/null
+++ b/src/front/pages/MapView.jsx
@@ -0,0 +1,106 @@
+import React, { useEffect, useState } from "react";
+import { GoogleMap, Marker, InfoWindow, useJsApiLoader } from "@react-google-maps/api";
+import { Spinner, Container } from "react-bootstrap";
+
+export const MapView = () => {
+ const [userLocation, setUserLocation] = useState(null);
+ const [activities, setActivities] = useState([]);
+ const [selectedActivity, setSelectedActivity] = useState(null);
+
+ const { isLoaded } = useJsApiLoader({
+ googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
+ });
+
+ useEffect(() => {
+ // Obtener la ubicación actual del usuario
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ const { latitude, longitude } = pos.coords;
+ setUserLocation({ lat: latitude, lng: longitude });
+ },
+ (err) => console.error("Error getting location:", err),
+ { enableHighAccuracy: true }
+ );
+
+ // Obtener las actividades del backend
+ const fetchActivities = async () => {
+ try {
+ const resp = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/activities`);
+ if (resp.ok) {
+ const data = await resp.json();
+ setActivities(data);
+ } else {
+ console.error("Error fetching activities");
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchActivities();
+ }, []);
+
+ if (!isLoaded || !userLocation)
+ return (
+
+
+
+ );
+
+ return (
+
+
+
setSelectedActivity(null)} // Cierra popup al hacer clic fuera
+ >
+ {/* Marcador de ubicación del usuario */}
+
+
+ {/* Marcadores de actividades */}
+ {activities.map((a) => (
+ setSelectedActivity(a)}
+ />
+ ))}
+
+ {/* Popup de detalles */}
+ {selectedActivity && (
+ setSelectedActivity(null)}
+ >
+
+
{selectedActivity.title}
+
+ Deporte: {selectedActivity.sport}
+
+
+ Fecha: {" "}
+ {new Date(selectedActivity.date).toLocaleDateString()}
+
+ {selectedActivity.description && (
+
{selectedActivity.description}
+ )}
+
+
+ )}
+
+
+
+ );
+};
diff --git a/src/front/pages/Register.jsx b/src/front/pages/Register.jsx
new file mode 100644
index 0000000000..abde4fb86e
--- /dev/null
+++ b/src/front/pages/Register.jsx
@@ -0,0 +1,85 @@
+
+// Registro: guarda datos en el estado local y simula registro.
+// Tras registrar, redirige a /login con ?registered=1 para mostrar aviso.
+
+import { useState } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import AuthShell from "../components/AuthShell";
+import TextInput from "../components/TextInput";
+
+export function Register() {
+ const nav = useNavigate();
+ const [form, setForm] = useState({
+ nombre: "",
+ apellidos: "",
+ email: "",
+ password: "",
+ telefono: "",
+ edad: "",
+ genero: "", // male | female | other
+ });
+ const [err, setErr] = useState("");
+
+ const onChange = (e) =>
+ setForm((f) => ({ ...f, [e.target.name]: e.target.value }));
+
+ const submit = (e) => {
+ e.preventDefault();
+ setErr("");
+
+ if (!form.nombre || !form.email || !form.password)
+ return setErr("Completa nombre, email y contraseña.");
+
+ if (form.password.length < 8)
+ return setErr("La contraseña debe tener al menos 8 caracteres.");
+
+ if (form.edad && (+form.edad < 18 || +form.edad > 100))
+ return setErr("Debes ser mayor de edad (18–100).");
+
+ if (form.genero === "")
+ return setErr("Selecciona un género.");
+
+ console.log("Register (solo UI):", form);
+ nav("/login?registered=1");
+ };
+
+ return (
+
+
+
+
+ ¿Ya tienes cuenta?
+ Inicia sesión
+
+
+ );
+}
diff --git a/src/front/routes.jsx b/src/front/routes.jsx
index 0557df6141..887a5be3ea 100644
--- a/src/front/routes.jsx
+++ b/src/front/routes.jsx
@@ -1,30 +1,24 @@
-// Import necessary components and functions from react-router-dom.
-
import {
- createBrowserRouter,
- createRoutesFromElements,
- Route,
+ createBrowserRouter,
+ createRoutesFromElements,
+ Route,
} from "react-router-dom";
import { Layout } from "./pages/Layout";
-import { Home } from "./pages/Home";
-import { Single } from "./pages/Single";
-import { Demo } from "./pages/Demo";
-
-export const router = createBrowserRouter(
- createRoutesFromElements(
- // CreateRoutesFromElements function allows you to build route elements declaratively.
- // Create your routes here, if you want to keep the Navbar and Footer in all views, add your new routes inside the containing Route.
- // Root, on the contrary, create a sister Route, if you have doubts, try it!
- // Note: keep in mind that errorElement will be the default page when you don't get a route, customize that page to make your project more attractive.
- // Note: The child paths of the Layout element replace the Outlet component with the elements contained in the "element" attribute of these child paths.
+import { Login } from "./pages/Login";
+import { Register } from "./pages/Register";
+import { Forgot } from "./pages/Forgot";
+import { MapView } from "./pages/MapView";
- // Root Route: All navigation will start from here.
- } errorElement={Not found! } >
- {/* Nested Routes: Defines sub-routes within the BaseHome component. */}
- } />
- } /> {/* Dynamic route for single items */}
- } />
-
- )
-);
\ No newline at end of file
+export const router = createBrowserRouter(
+ createRoutesFromElements(
+ } errorElement={Not found! }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ )
+);