diff --git a/.env.example b/.env.example
index 05a32a5146..9508349d3e 100644
--- a/.env.example
+++ b/.env.example
@@ -11,4 +11,4 @@ DEBUG=TRUE
# Front-End Variables
VITE_BASENAME=/
-#VITE_BACKEND_URL=
+VITE_BACKEND_URL=https://obscure-funicular-7v4g9jg94jwpfw56-3001.app.github.dev/
diff --git a/.gitignore b/.gitignore
index 80704f4378..02b2f03e93 100755
--- a/.gitignore
+++ b/.gitignore
@@ -79,4 +79,4 @@ dist/*
database.database
database.db
diagram.png
-__pycache__/
+__pycache__/
\ No newline at end of file
diff --git a/= b/=
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/Pipfile b/Pipfile
index 4d377014ae..a18ba77433 100644
--- a/Pipfile
+++ b/Pipfile
@@ -17,12 +17,16 @@ gunicorn = "*"
cloudinary = "*"
flask-admin = "==2.0.0"
typing-extensions = "*"
-flask-jwt-extended = "==4.6.0"
wtforms = "==3.1.2"
sqlalchemy = "*"
+flask-bcrypt = "*"
+flask-jwt-extended = "*"
+flask-mail = "*"
+resend = "*"
+tomli = "*"
[requires]
-python_version = "3.13"
+python_version = "3.10"
[scripts]
start="flask run -p 3001 -h 0.0.0.0"
diff --git a/Pipfile.lock b/Pipfile.lock
index d9e474e972..b98fc1b658 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
- "sha256": "ffbfb32d0afa5e4bcaba5c2d08c81381a97abd90f22284d2b76647365df5dc50"
+ "sha256": "4a95eb1c7bd5af04db52c82f5ceeedb63cb4739f64dcffca4aa53c540d2dc983"
},
"pipfile-spec": 6,
"requires": {
- "python_version": "3.13"
+ "python_version": "3.10"
},
"sources": [
{
@@ -18,11 +18,80 @@
"default": {
"alembic": {
"hashes": [
- "sha256:8a289f6778262df31571d29cca4c7fbacd2f0f582ea0816f4c399b6da7528486",
- "sha256:cbc2386e60f89608bb63f30d2d6cc66c7aaed1fe105bd862828600e5ad167023"
+ "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a",
+ "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc"
],
"markers": "python_version >= '3.10'",
- "version": "==1.17.1"
+ "version": "==1.18.4"
+ },
+ "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": [
@@ -34,19 +103,154 @@
},
"certifi": {
"hashes": [
- "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de",
- "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"
+ "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa",
+ "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"
],
"markers": "python_version >= '3.7'",
- "version": "==2025.10.5"
+ "version": "==2026.2.25"
+ },
+ "charset-normalizer": {
+ "hashes": [
+ "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e",
+ "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c",
+ "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5",
+ "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815",
+ "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f",
+ "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0",
+ "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484",
+ "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407",
+ "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6",
+ "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8",
+ "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264",
+ "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815",
+ "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2",
+ "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4",
+ "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579",
+ "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f",
+ "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa",
+ "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95",
+ "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab",
+ "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297",
+ "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a",
+ "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e",
+ "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84",
+ "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8",
+ "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0",
+ "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9",
+ "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f",
+ "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1",
+ "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843",
+ "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565",
+ "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7",
+ "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c",
+ "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b",
+ "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7",
+ "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687",
+ "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9",
+ "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14",
+ "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89",
+ "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f",
+ "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0",
+ "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9",
+ "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a",
+ "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389",
+ "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0",
+ "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30",
+ "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd",
+ "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e",
+ "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9",
+ "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc",
+ "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532",
+ "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d",
+ "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae",
+ "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2",
+ "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64",
+ "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f",
+ "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557",
+ "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e",
+ "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff",
+ "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398",
+ "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db",
+ "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a",
+ "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43",
+ "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597",
+ "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c",
+ "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e",
+ "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2",
+ "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54",
+ "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e",
+ "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4",
+ "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4",
+ "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7",
+ "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6",
+ "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5",
+ "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194",
+ "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69",
+ "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f",
+ "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316",
+ "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e",
+ "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73",
+ "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8",
+ "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923",
+ "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88",
+ "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f",
+ "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21",
+ "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4",
+ "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6",
+ "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc",
+ "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2",
+ "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866",
+ "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021",
+ "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2",
+ "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d",
+ "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8",
+ "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de",
+ "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237",
+ "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4",
+ "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778",
+ "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb",
+ "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc",
+ "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602",
+ "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4",
+ "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f",
+ "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5",
+ "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611",
+ "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8",
+ "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf",
+ "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d",
+ "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b",
+ "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db",
+ "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e",
+ "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077",
+ "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd",
+ "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef",
+ "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e",
+ "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8",
+ "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe",
+ "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058",
+ "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17",
+ "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833",
+ "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421",
+ "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550",
+ "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff",
+ "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2",
+ "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc",
+ "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982",
+ "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d",
+ "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed",
+ "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104",
+ "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==3.4.6"
},
"click": {
"hashes": [
- "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc",
- "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"
+ "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a",
+ "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"
],
"markers": "python_version >= '3.10'",
- "version": "==8.3.0"
+ "version": "==8.3.1"
},
"cloudinary": {
"hashes": [
@@ -58,12 +262,12 @@
},
"flask": {
"hashes": [
- "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87",
- "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c"
+ "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb",
+ "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
- "version": "==3.1.2"
+ "version": "==3.1.3"
},
"flask-admin": {
"hashes": [
@@ -74,23 +278,40 @@
"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",
- "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db"
+ "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423",
+ "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a"
],
"index": "pypi",
"markers": "python_version >= '3.9' and python_version < '4.0'",
- "version": "==6.0.1"
+ "version": "==6.0.2"
},
"flask-jwt-extended": {
"hashes": [
- "sha256:63a28fc9731bcc6c4b8815b6f954b5904caa534fc2ae9b93b1d3ef12930dca95",
- "sha256:9215d05a9413d3855764bcd67035e75819d23af2fafb6b55197eb5a3313fdfb2"
+ "sha256:52f35bf0985354d7fb7b876e2eb0e0b141aaff865a22ff6cc33d9a18aa987978",
+ "sha256:8085d6757505b6f3291a2638c84d207e8f0ad0de662d1f46aa2f77e658a0c976"
],
"index": "pypi",
- "markers": "python_version >= '3.7' and python_version < '4'",
- "version": "==4.6.0"
+ "markers": "python_version >= '3.9' and python_version < '4'",
+ "version": "==4.7.1"
+ },
+ "flask-mail": {
+ "hashes": [
+ "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d",
+ "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==0.10.0"
},
"flask-migrate": {
"hashes": [
@@ -120,72 +341,79 @@
},
"greenlet": {
"hashes": [
- "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b",
- "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735",
- "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079",
- "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d",
- "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433",
- "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58",
- "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52",
- "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31",
- "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246",
- "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f",
- "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671",
- "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8",
- "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d",
- "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f",
- "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0",
- "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd",
- "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337",
- "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0",
- "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633",
- "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b",
- "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa",
- "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31",
- "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9",
- "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b",
- "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4",
- "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc",
- "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c",
- "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98",
- "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f",
- "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c",
- "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590",
- "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3",
- "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2",
- "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9",
- "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5",
- "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02",
- "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0",
- "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1",
- "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c",
- "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594",
- "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5",
- "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d",
- "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a",
- "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6",
- "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b",
- "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df",
- "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945",
- "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae",
- "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb",
- "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504",
- "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb",
- "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01",
- "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c",
- "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"
+ "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd",
+ "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082",
+ "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b",
+ "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5",
+ "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f",
+ "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727",
+ "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e",
+ "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2",
+ "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f",
+ "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327",
+ "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd",
+ "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2",
+ "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070",
+ "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99",
+ "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be",
+ "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79",
+ "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7",
+ "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e",
+ "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf",
+ "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f",
+ "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506",
+ "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a",
+ "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395",
+ "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4",
+ "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca",
+ "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492",
+ "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab",
+ "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358",
+ "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce",
+ "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5",
+ "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef",
+ "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d",
+ "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac",
+ "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55",
+ "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124",
+ "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4",
+ "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986",
+ "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd",
+ "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f",
+ "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb",
+ "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4",
+ "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13",
+ "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab",
+ "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff",
+ "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a",
+ "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9",
+ "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86",
+ "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd",
+ "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71",
+ "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92",
+ "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643",
+ "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54",
+ "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"
],
- "markers": "python_version >= '3.9'",
- "version": "==3.2.4"
+ "markers": "python_version >= '3.10'",
+ "version": "==3.3.2"
},
"gunicorn": {
"hashes": [
- "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d",
- "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"
+ "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616",
+ "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b"
],
"index": "pypi",
- "markers": "python_version >= '3.7'",
- "version": "==23.0.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==25.1.0"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea",
+ "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==3.11"
},
"itsdangerous": {
"hashes": [
@@ -308,11 +536,11 @@
},
"packaging": {
"hashes": [
- "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484",
- "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"
+ "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4",
+ "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"
],
"markers": "python_version >= '3.8'",
- "version": "==25.0"
+ "version": "==26.0"
},
"psycopg2-binary": {
"hashes": [
@@ -390,20 +618,20 @@
},
"pyjwt": {
"hashes": [
- "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953",
- "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"
+ "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c",
+ "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"
],
"markers": "python_version >= '3.9'",
- "version": "==2.10.1"
+ "version": "==2.12.1"
},
"python-dotenv": {
"hashes": [
- "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6",
- "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"
+ "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a",
+ "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"
],
"index": "pypi",
- "markers": "python_version >= '3.9'",
- "version": "==1.2.1"
+ "markers": "python_version >= '3.10'",
+ "version": "==1.2.2"
},
"pyyaml": {
"hashes": [
@@ -484,6 +712,23 @@
"markers": "python_version >= '3.8'",
"version": "==6.0.3"
},
+ "requests": {
+ "hashes": [
+ "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6",
+ "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==2.32.5"
+ },
+ "resend": {
+ "hashes": [
+ "sha256:5e25a804a84a68df504f2ade5369ac37e0139e37788a1f20b66c88696595b4bc",
+ "sha256:957a6a59dc597ce27fbd6d5383220dd9cc497fab99d4f3d775c8a42a449a569e"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.7'",
+ "version": "==2.26.0"
+ },
"six": {
"hashes": [
"sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
@@ -494,67 +739,127 @@
},
"sqlalchemy": {
"hashes": [
- "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7",
- "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22",
- "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45",
- "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd",
- "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1",
- "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0",
- "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d",
- "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749",
- "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05",
- "sha256:1df4763760d1de0dfc8192cc96d8aa293eb1a44f8f7a5fbe74caf1b551905c5e",
- "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40",
- "sha256:22be14009339b8bc16d6b9dc8780bacaba3402aa7581658e246114abbd2236e3",
- "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3",
- "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976",
- "sha256:2bf4bb6b3d6228fcf3a71b50231199fb94d2dd2611b66d33be0578ea3e6c2726",
- "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d",
- "sha256:2f19644f27c76f07e10603580a47278abb2a70311136a7f8fd27dc2e096b9013",
- "sha256:2fc44e5965ea46909a416fff0af48a219faefd5773ab79e5f8a5fcd5d62b2667",
- "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165",
- "sha256:3255d821ee91bdf824795e936642bbf43a4c7cedf5d1aed8d24524e66843aa74",
- "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399",
- "sha256:357bade0e46064f88f2c3a99808233e67b0051cdddf82992379559322dfeb183",
- "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a",
- "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e",
- "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9",
- "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632",
- "sha256:4848395d932e93c1595e59a8672aa7400e8922c39bb9b0668ed99ac6fa867822",
- "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f",
- "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985",
- "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26",
- "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29",
- "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b",
- "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250",
- "sha256:78e6c137ba35476adb5432103ae1534f2f5295605201d946a4198a0dea4b38e7",
- "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5",
- "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce",
- "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e",
- "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4",
- "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6",
- "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5",
- "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44",
- "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100",
- "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c",
- "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e",
- "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e",
- "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2",
- "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2",
- "sha256:d733dec0614bb8f4bcb7c8af88172b974f685a31dc3a65cca0527e3120de5606",
- "sha256:dc8b3850d2a601ca2320d081874033684e246d28e1c5e89db0864077cfc8f5a9",
- "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa",
- "sha256:e998cf7c29473bd077704cea3577d23123094311f59bdc4af551923b168332b1",
- "sha256:ebac3f0b5732014a126b43c2b7567f2f0e0afea7d9119a3378bde46d3dcad88e",
- "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73",
- "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0",
- "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4",
- "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e",
- "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1"
+ "sha256:01f6bbd4308b23240cf7d3ef117557c8fd097ec9549d5d8a52977544e35b40ad",
+ "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e",
+ "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd",
+ "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6",
+ "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0",
+ "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0",
+ "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc",
+ "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b",
+ "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f",
+ "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0",
+ "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894",
+ "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b",
+ "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8",
+ "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0",
+ "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131",
+ "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b",
+ "sha256:4599a95f9430ae0de82b52ff0d27304fe898c17cb5f4099f7438a51b9998ac77",
+ "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f",
+ "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb",
+ "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9",
+ "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c",
+ "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241",
+ "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658",
+ "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7",
+ "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a",
+ "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae",
+ "sha256:6bb85c546591569558571aa1b06aba711b26ae62f111e15e56136d69920e1616",
+ "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7",
+ "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89",
+ "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3",
+ "sha256:7c998f2ace8bf76b453b75dbcca500d4f4b9dd3908c13e89b86289b37784848b",
+ "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0",
+ "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2",
+ "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d",
+ "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76",
+ "sha256:858e433f12b0e5b3ed2f8da917433b634f4937d0e8793e5cb33c54a1a01df565",
+ "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99",
+ "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485",
+ "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617",
+ "sha256:a5b429eb84339f9f05e06083f119ad814e6d85e27ecbdf9c551dfdbb128eaf8a",
+ "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096",
+ "sha256:a6b764fb312bd35e47797ad2e63f0d323792837a6ac785a4ca967019357d2bc7",
+ "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed",
+ "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f",
+ "sha256:b8fc3454b4f3bd0a368001d0e968852dad45a873f8b4babd41bc302ec851a099",
+ "sha256:bcb8ebbf2e2c36cfe01a94f2438012c6a9d494cf80f129d9753bcdf33bfc35a6",
+ "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018",
+ "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2",
+ "sha256:d64177f443594c8697369c10e4bbcac70ef558e0f7921a1de7e4a3d1734bcf67",
+ "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933",
+ "sha256:d8fcccbbc0c13c13702c471da398b8cd72ba740dca5859f148ae8e0e8e0d3e7e",
+ "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b",
+ "sha256:e214d546c8ecb5fc22d6e6011746082abf13a9cf46eefb45769c7b31407c97b5",
+ "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd",
+ "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79",
+ "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4",
+ "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571",
+ "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c",
+ "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121",
+ "sha256:f27f9da0a7d22b9f981108fd4b62f8b5743423388915a563e651c20d06c1f457",
+ "sha256:f8649a14caa5f8a243628b1d61cf530ad9ae4578814ba726816adb1121fc493e",
+ "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29",
+ "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==2.0.44"
+ "version": "==2.0.48"
+ },
+ "tomli": {
+ "hashes": [
+ "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729",
+ "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b",
+ "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d",
+ "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df",
+ "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576",
+ "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d",
+ "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1",
+ "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a",
+ "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e",
+ "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc",
+ "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702",
+ "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6",
+ "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd",
+ "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4",
+ "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776",
+ "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a",
+ "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66",
+ "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87",
+ "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2",
+ "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f",
+ "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475",
+ "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f",
+ "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95",
+ "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9",
+ "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3",
+ "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9",
+ "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76",
+ "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da",
+ "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8",
+ "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51",
+ "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86",
+ "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8",
+ "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0",
+ "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b",
+ "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1",
+ "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e",
+ "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d",
+ "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c",
+ "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867",
+ "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a",
+ "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c",
+ "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0",
+ "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4",
+ "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614",
+ "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132",
+ "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa",
+ "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==2.4.0"
},
"typing-extensions": {
"hashes": [
@@ -567,19 +872,19 @@
},
"urllib3": {
"hashes": [
- "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760",
- "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"
+ "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed",
+ "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"
],
"markers": "python_version >= '3.9'",
- "version": "==2.5.0"
+ "version": "==2.6.3"
},
"werkzeug": {
"hashes": [
- "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e",
- "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"
+ "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f",
+ "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351"
],
"markers": "python_version >= '3.9'",
- "version": "==3.1.3"
+ "version": "==3.1.7"
},
"wtforms": {
"hashes": [
diff --git a/index.html b/index.html
index 27a99f796e..425696363d 100644
--- a/index.html
+++ b/index.html
@@ -1,12 +1,13 @@
-
+
-
+
+
- Hello Rigo
+ Pomify
diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/0763d677d453_.py
deleted file mode 100644
index 88964176f1..0000000000
--- a/migrations/versions/0763d677d453_.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""empty message
-
-Revision ID: 0763d677d453
-Revises:
-Create Date: 2025-02-25 14:47:16.337069
-
-"""
-from alembic import op
-import sqlalchemy as sa
-
-
-# revision identifiers, used by Alembic.
-revision = '0763d677d453'
-down_revision = None
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
- # ### commands auto generated by Alembic - please adjust! ###
- op.create_table('user',
- sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('email', sa.String(length=120), nullable=False),
- sa.Column('password', sa.String(), nullable=False),
- sa.Column('is_active', sa.Boolean(), nullable=False),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('email')
- )
- # ### end Alembic commands ###
-
-
-def downgrade():
- # ### commands auto generated by Alembic - please adjust! ###
- op.drop_table('user')
- # ### end Alembic commands ###
diff --git a/migrations/versions/592c49218df5_.py b/migrations/versions/592c49218df5_.py
new file mode 100644
index 0000000000..ce0bf4e426
--- /dev/null
+++ b/migrations/versions/592c49218df5_.py
@@ -0,0 +1,79 @@
+"""empty message
+
+Revision ID: 592c49218df5
+Revises: 0763d677d453
+Create Date: 2026-03-20 09:32:51.994792
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '592c49218df5'
+down_revision = '0763d677d453'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('user_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('name', sa.String(length=120), nullable=True),
+ sa.Column('email', sa.String(length=120), nullable=False),
+ sa.Column('password_hash', sa.String(), nullable=False),
+ sa.Column('avatar_url', sa.String(length=500), nullable=True),
+ sa.Column('reset_token', sa.String(length=500), nullable=True),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('avatar_url'),
+ sa.UniqueConstraint('email')
+ )
+ op.create_table('folder_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.ForeignKeyConstraint(['user_id'], ['user_table.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('goal_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('content', sa.Text(), nullable=False),
+ sa.Column('status', sa.String(length=20), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['user_id'], ['user_table.id'], ),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('title')
+ )
+ op.create_table('page_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('content', sa.Text(), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('update_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('folder_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['folder_id'], ['folder_table.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.drop_table('user')
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('user',
+ sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
+ sa.Column('email', sa.VARCHAR(length=120), autoincrement=False, nullable=False),
+ sa.Column('password', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('is_active', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name=op.f('user_pkey')),
+ sa.UniqueConstraint('email', name=op.f('user_email_key'), postgresql_include=[], postgresql_nulls_not_distinct=False)
+ )
+ op.drop_table('page_table')
+ op.drop_table('goal_table')
+ op.drop_table('folder_table')
+ op.drop_table('user_table')
+ # ### end Alembic commands ###
diff --git a/migrations/versions/b9cc2c3efd90_.py b/migrations/versions/b9cc2c3efd90_.py
new file mode 100644
index 0000000000..c964f5f9a8
--- /dev/null
+++ b/migrations/versions/b9cc2c3efd90_.py
@@ -0,0 +1,70 @@
+"""empty message
+
+Revision ID: b9cc2c3efd90
+Revises:
+Create Date: 2026-03-20 10:48:43.696738
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'b9cc2c3efd90'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('user_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('name', sa.String(length=120), nullable=True),
+ sa.Column('email', sa.String(length=120), nullable=False),
+ sa.Column('password_hash', sa.String(), nullable=False),
+ sa.Column('avatar_url', sa.String(length=500), nullable=True),
+ sa.Column('reset_token', sa.String(length=500), nullable=True),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('avatar_url'),
+ sa.UniqueConstraint('email')
+ )
+ op.create_table('folder_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.ForeignKeyConstraint(['user_id'], ['user_table.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('goal_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('content', sa.Text(), nullable=False),
+ sa.Column('status', sa.String(length=20), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['user_id'], ['user_table.id'], ),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('title')
+ )
+ op.create_table('page_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('title', sa.String(length=120), nullable=False),
+ sa.Column('content', sa.Text(), nullable=False),
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('update_at', sa.DateTime(timezone=True), nullable=False),
+ sa.Column('folder_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['folder_id'], ['folder_table.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('page_table')
+ op.drop_table('goal_table')
+ op.drop_table('folder_table')
+ op.drop_table('user_table')
+ # ### end Alembic commands ###
diff --git a/package-lock.json b/package-lock.json
index 8d43d98ab7..ffef2e68dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,7 +1,7 @@
{
"name": "react-hello-webapp",
"version": "1.0.1",
- "lockfileVersion": 2,
+ "lockfileVersion": 3,
"requires": true,
"packages": {
"": {
@@ -9,6 +9,8 @@
"version": "1.0.1",
"license": "ISC",
"dependencies": {
+ "lenis": "^1.3.20",
+ "motion": "^12.35.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -877,19 +879,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -997,14 +986,6 @@
"@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/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
@@ -1307,14 +1288,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "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
- },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2287,6 +2260,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.35.2",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.2.tgz",
+ "integrity": "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.35.2",
+ "motion-utils": "^12.29.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -2294,11 +2294,12 @@
"dev": true
},
"node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -3099,6 +3100,37 @@
"node": ">=4.0"
}
},
+ "node_modules/lenis": {
+ "version": "1.3.20",
+ "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.20.tgz",
+ "integrity": "sha512-sN5+fCQjovD/KZcClwmVLqLIufiHM9w9I/wXPHKUuRLtRa370qdHI2r2WIetSBS/9Sn5yTAGdhxb7W9AtD9x8g==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/*",
+ "playground",
+ "playground/*"
+ ],
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/darkroomengineering"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": ">=3.0.0",
+ "react": ">=17.0.0",
+ "vue": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ }
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3153,6 +3185,47 @@
"node": "*"
}
},
+ "node_modules/motion": {
+ "version": "12.35.2",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.35.2.tgz",
+ "integrity": "sha512-8zCi1DkNyU6a/tgEHn/GnnXZDcaMpDHbDOGORY1Rg/6lcNMSOuvwDB3i4hMSOvxqMWArc/vrGaw/Xek1OP69/A==",
+ "license": "MIT",
+ "dependencies": {
+ "framer-motion": "^12.35.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.35.2",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.2.tgz",
+ "integrity": "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.29.2"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.29.2",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
+ "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -3915,29 +3988,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "optional": true,
- "peer": true,
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
@@ -4074,41 +4124,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/terser": {
- "version": "5.38.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz",
- "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "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
- },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"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",
@@ -4466,2869 +4493,5 @@
"url": "https://github.com/sponsors/sindresorhus"
}
}
- },
- "dependencies": {
- "@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "requires": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
- "dev": true,
- "requires": {
- "@babel/helper-validator-identifier": "^7.25.9",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- }
- },
- "@babel/compat-data": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
- "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
- "dev": true
- },
- "@babel/core": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
- "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
- "dev": true,
- "requires": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/helper-compilation-targets": "^7.26.5",
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.7",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/traverse": "^7.26.7",
- "@babel/types": "^7.26.7",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "dependencies": {
- "convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true
- }
- }
- },
- "@babel/generator": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
- "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
- "dev": true,
- "requires": {
- "@babel/parser": "^7.26.5",
- "@babel/types": "^7.26.5",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^3.0.2"
- }
- },
- "@babel/helper-compilation-targets": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
- "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
- "dev": true,
- "requires": {
- "@babel/compat-data": "^7.26.5",
- "@babel/helper-validator-option": "^7.25.9",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "dependencies": {
- "lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "requires": {
- "yallist": "^3.0.2"
- }
- },
- "yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true
- }
- }
- },
- "@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
- "dev": true,
- "requires": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- }
- },
- "@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
- "dev": true,
- "requires": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- }
- },
- "@babel/helper-plugin-utils": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
- "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
- "dev": true
- },
- "@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
- "dev": true
- },
- "@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
- "dev": true
- },
- "@babel/helper-validator-option": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
- "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
- "dev": true
- },
- "@babel/helpers": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
- "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
- "dev": true,
- "requires": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7"
- }
- },
- "@babel/parser": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
- "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.26.7"
- }
- },
- "@babel/plugin-transform-react-jsx-self": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
- "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.25.9"
- }
- },
- "@babel/plugin-transform-react-jsx-source": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
- "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.25.9"
- }
- },
- "@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
- }
- },
- "@babel/traverse": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
- "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
- }
- },
- "@babel/types": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
- "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
- "dev": true,
- "requires": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
- }
- },
- "@esbuild/android-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
- "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
- "dev": true,
- "optional": true
- },
- "@esbuild/android-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
- "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/android-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
- "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
- "dev": true,
- "optional": true
- },
- "@esbuild/darwin-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
- "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
- "dev": true,
- "optional": true
- },
- "@esbuild/darwin-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
- "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/freebsd-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
- "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
- "dev": true,
- "optional": true
- },
- "@esbuild/freebsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
- "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
- "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
- "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
- "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-loong64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
- "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-mips64el": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
- "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-ppc64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
- "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-riscv64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
- "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-s390x": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
- "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/linux-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
- "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
- "dev": true,
- "optional": true
- },
- "@esbuild/netbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
- "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
- "dev": true,
- "optional": true
- },
- "@esbuild/openbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
- "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
- "dev": true,
- "optional": true
- },
- "@esbuild/sunos-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
- "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
- "dev": true,
- "optional": true
- },
- "@esbuild/win32-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
- "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
- "dev": true,
- "optional": true
- },
- "@esbuild/win32-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
- "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
- "dev": true,
- "optional": true
- },
- "@esbuild/win32-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
- "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
- "dev": true,
- "optional": true
- },
- "@eslint-community/eslint-utils": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "dependencies": {
- "eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true
- }
- }
- },
- "@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true
- },
- "@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
- "dev": true,
- "requires": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "dependencies": {
- "globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "requires": {
- "type-fest": "^0.20.2"
- }
- }
- }
- },
- "@eslint/js": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
- "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
- "dev": true
- },
- "@humanwhocodes/config-array": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
- "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
- "dev": true,
- "requires": {
- "@humanwhocodes/object-schema": "^2.0.3",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
- }
- },
- "@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true
- },
- "@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "dev": true
- },
- "@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "dev": true,
- "requires": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true
- },
- "@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true
- },
- "@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "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"
- }
- },
- "@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true
- },
- "@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "requires": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "requires": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- }
- },
- "@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true
- },
- "@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "requires": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- }
- },
- "@remix-run/router": {
- "version": "1.22.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
- "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw=="
- },
- "@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "requires": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "requires": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
- "dev": true,
- "requires": {
- "@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/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
- },
- "@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,
- "requires": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
- }
- },
- "@types/react-dom": {
- "version": "18.3.5",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
- "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
- "dev": true,
- "requires": {}
- },
- "@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "dev": true
- },
- "@vitejs/plugin-react": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
- "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
- "dev": true,
- "requires": {
- "@babel/core": "^7.26.0",
- "@babel/plugin-transform-react-jsx-self": "^7.25.9",
- "@babel/plugin-transform-react-jsx-source": "^7.25.9",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.14.2"
- }
- },
- "acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "dev": true
- },
- "acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
- },
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "array-buffer-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
- "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "is-array-buffer": "^3.0.5"
- }
- },
- "array-includes": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
- "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "is-string": "^1.0.7"
- }
- },
- "array.prototype.findlast": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
- "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-shim-unscopables": "^1.0.2"
- }
- },
- "array.prototype.flatmap": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
- "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- }
- },
- "array.prototype.tosorted": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
- "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3",
- "es-errors": "^1.3.0",
- "es-shim-unscopables": "^1.0.2"
- }
- },
- "arraybuffer.prototype.slice": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
- "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
- "dev": true,
- "requires": {
- "array-buffer-byte-length": "^1.0.1",
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "is-array-buffer": "^3.0.4"
- }
- },
- "async-function": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
- "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
- "dev": true
- },
- "available-typed-arrays": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
- "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
- "dev": true,
- "requires": {
- "possible-typed-array-names": "^1.0.0"
- }
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "browserslist": {
- "version": "4.24.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
- "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
- "dev": true,
- "requires": {
- "caniuse-lite": "^1.0.30001688",
- "electron-to-chromium": "^1.5.73",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.1"
- }
- },
- "buffer-from": {
- "version": "1.1.2",
- "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
- },
- "call-bind": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
- "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
- "dev": true,
- "requires": {
- "call-bind-apply-helpers": "^1.0.0",
- "es-define-property": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "set-function-length": "^1.2.2"
- }
- },
- "call-bind-apply-helpers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
- "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- }
- },
- "call-bound": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
- "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
- "dev": true,
- "requires": {
- "call-bind-apply-helpers": "^1.0.1",
- "get-intrinsic": "^1.2.6"
- }
- },
- "callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true
- },
- "caniuse-lite": {
- "version": "1.0.30001697",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz",
- "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==",
- "dev": true
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "requires": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- }
- },
- "csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
- },
- "data-view-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
- "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- }
- },
- "data-view-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
- "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- }
- },
- "data-view-byte-offset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
- "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.1"
- }
- },
- "debug": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
- "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- }
- },
- "deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true
- },
- "define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
- "requires": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- }
- },
- "define-properties": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
- "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
- "requires": {
- "define-data-property": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- }
- },
- "doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "requires": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- }
- },
- "electron-to-chromium": {
- "version": "1.5.93",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.93.tgz",
- "integrity": "sha512-M+29jTcfNNoR9NV7la4SwUqzWAxEwnc7ThA5e1m6LRSotmpfpCpLcIfgtSCVL+MllNLgAyM/5ru86iMRemPzDQ==",
- "dev": true
- },
- "es-abstract": {
- "version": "1.23.9",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
- "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
- "dev": true,
- "requires": {
- "array-buffer-byte-length": "^1.0.2",
- "arraybuffer.prototype.slice": "^1.0.4",
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "data-view-buffer": "^1.0.2",
- "data-view-byte-length": "^1.0.2",
- "data-view-byte-offset": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-set-tostringtag": "^2.1.0",
- "es-to-primitive": "^1.3.0",
- "function.prototype.name": "^1.1.8",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.0",
- "get-symbol-description": "^1.1.0",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "internal-slot": "^1.1.0",
- "is-array-buffer": "^3.0.5",
- "is-callable": "^1.2.7",
- "is-data-view": "^1.0.2",
- "is-regex": "^1.2.1",
- "is-shared-array-buffer": "^1.0.4",
- "is-string": "^1.1.1",
- "is-typed-array": "^1.1.15",
- "is-weakref": "^1.1.0",
- "math-intrinsics": "^1.1.0",
- "object-inspect": "^1.13.3",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.7",
- "own-keys": "^1.0.1",
- "regexp.prototype.flags": "^1.5.3",
- "safe-array-concat": "^1.1.3",
- "safe-push-apply": "^1.0.0",
- "safe-regex-test": "^1.1.0",
- "set-proto": "^1.0.0",
- "string.prototype.trim": "^1.2.10",
- "string.prototype.trimend": "^1.0.9",
- "string.prototype.trimstart": "^1.0.8",
- "typed-array-buffer": "^1.0.3",
- "typed-array-byte-length": "^1.0.3",
- "typed-array-byte-offset": "^1.0.4",
- "typed-array-length": "^1.0.7",
- "unbox-primitive": "^1.1.0",
- "which-typed-array": "^1.1.18"
- }
- },
- "es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true
- },
- "es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true
- },
- "es-iterator-helpers": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
- "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-set-tostringtag": "^2.0.3",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.6",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "iterator.prototype": "^1.1.4",
- "safe-array-concat": "^1.1.3"
- }
- },
- "es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0"
- }
- },
- "es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- }
- },
- "es-shim-unscopables": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
- "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
- "dev": true,
- "requires": {
- "hasown": "^2.0.0"
- }
- },
- "es-to-primitive": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
- "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
- "dev": true,
- "requires": {
- "is-callable": "^1.2.7",
- "is-date-object": "^1.0.5",
- "is-symbol": "^1.0.4"
- }
- },
- "esbuild": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
- "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
- "dev": true,
- "requires": {
- "@esbuild/android-arm": "0.18.20",
- "@esbuild/android-arm64": "0.18.20",
- "@esbuild/android-x64": "0.18.20",
- "@esbuild/darwin-arm64": "0.18.20",
- "@esbuild/darwin-x64": "0.18.20",
- "@esbuild/freebsd-arm64": "0.18.20",
- "@esbuild/freebsd-x64": "0.18.20",
- "@esbuild/linux-arm": "0.18.20",
- "@esbuild/linux-arm64": "0.18.20",
- "@esbuild/linux-ia32": "0.18.20",
- "@esbuild/linux-loong64": "0.18.20",
- "@esbuild/linux-mips64el": "0.18.20",
- "@esbuild/linux-ppc64": "0.18.20",
- "@esbuild/linux-riscv64": "0.18.20",
- "@esbuild/linux-s390x": "0.18.20",
- "@esbuild/linux-x64": "0.18.20",
- "@esbuild/netbsd-x64": "0.18.20",
- "@esbuild/openbsd-x64": "0.18.20",
- "@esbuild/sunos-x64": "0.18.20",
- "@esbuild/win32-arm64": "0.18.20",
- "@esbuild/win32-ia32": "0.18.20",
- "@esbuild/win32-x64": "0.18.20"
- }
- },
- "escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true
- },
- "eslint": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
- "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
- "dev": true,
- "requires": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.1",
- "@humanwhocodes/config-array": "^0.13.0",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
- "debug": "^4.3.2",
- "doctrine": "^3.0.0",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
- },
- "eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true
- },
- "find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "requires": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.3"
- }
- },
- "globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "requires": {
- "type-fest": "^0.20.2"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "requires": {
- "p-locate": "^5.0.0"
- }
- },
- "p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "requires": {
- "yocto-queue": "^0.1.0"
- }
- },
- "p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "requires": {
- "p-limit": "^3.0.2"
- }
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
- }
- },
- "eslint-plugin-react": {
- "version": "7.37.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz",
- "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.8",
- "array.prototype.findlast": "^1.2.5",
- "array.prototype.flatmap": "^1.3.3",
- "array.prototype.tosorted": "^1.1.4",
- "doctrine": "^2.1.0",
- "es-iterator-helpers": "^1.2.1",
- "estraverse": "^5.3.0",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.8",
- "object.fromentries": "^2.0.8",
- "object.values": "^1.2.1",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.5",
- "semver": "^6.3.1",
- "string.prototype.matchall": "^4.0.12",
- "string.prototype.repeat": "^1.0.0"
- },
- "dependencies": {
- "doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "resolve": {
- "version": "2.0.0-next.5",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
- "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- }
- }
- },
- "eslint-plugin-react-hooks": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
- "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
- "dev": true,
- "requires": {}
- },
- "eslint-plugin-react-refresh": {
- "version": "0.4.18",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
- "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
- "dev": true,
- "requires": {}
- },
- "eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
- "dev": true,
- "requires": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- }
- },
- "espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
- "dev": true,
- "requires": {
- "acorn": "^8.9.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
- },
- "dependencies": {
- "eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true
- }
- }
- },
- "esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "requires": {
- "estraverse": "^5.1.0"
- }
- },
- "esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "requires": {
- "estraverse": "^5.2.0"
- }
- },
- "estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true
- },
- "esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true
- },
- "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
- },
- "fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true
- },
- "fastq": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
- "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
- "dev": true,
- "requires": {
- "reusify": "^1.0.4"
- }
- },
- "file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
- "dev": true,
- "requires": {
- "flat-cache": "^3.0.4"
- }
- },
- "flat-cache": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
- "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
- "dev": true,
- "requires": {
- "flatted": "^3.1.0",
- "rimraf": "^3.0.2"
- }
- },
- "flatted": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
- "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
- "dev": true
- },
- "for-each": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz",
- "integrity": "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==",
- "dev": true,
- "requires": {
- "is-callable": "^1.2.7"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "dev": true
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
- "function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true
- },
- "function.prototype.name": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
- "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "functions-have-names": "^1.2.3",
- "hasown": "^2.0.2",
- "is-callable": "^1.2.7"
- }
- },
- "functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true
- },
- "gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true
- },
- "get-intrinsic": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
- "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
- "dev": true,
- "requires": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.0",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- }
- },
- "get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "requires": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "get-symbol-description": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
- "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6"
- }
- },
- "glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true
- },
- "globalthis": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
- "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
- "dev": true,
- "requires": {
- "define-properties": "^1.2.1",
- "gopd": "^1.0.1"
- }
- },
- "gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true
- },
- "graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true
- },
- "has-bigints": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
- "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
- "dev": true
- },
- "has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
- "requires": {
- "es-define-property": "^1.0.0"
- }
- },
- "has-proto": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
- "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
- "dev": true,
- "requires": {
- "dunder-proto": "^1.0.0"
- }
- },
- "has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true
- },
- "has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
- "requires": {
- "has-symbols": "^1.0.3"
- }
- },
- "hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "requires": {
- "function-bind": "^1.1.2"
- }
- },
- "ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true
- },
- "import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
- "requires": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- }
- },
- "imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dev": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "internal-slot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
- "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.2",
- "side-channel": "^1.1.0"
- }
- },
- "is-array-buffer": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
- "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- }
- },
- "is-async-function": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
- "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
- "dev": true,
- "requires": {
- "async-function": "^1.0.0",
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- }
- },
- "is-bigint": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
- "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
- "dev": true,
- "requires": {
- "has-bigints": "^1.0.2"
- }
- },
- "is-boolean-object": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
- "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- }
- },
- "is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true
- },
- "is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
- "requires": {
- "hasown": "^2.0.2"
- }
- },
- "is-data-view": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
- "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "is-typed-array": "^1.1.13"
- }
- },
- "is-date-object": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
- "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "has-tostringtag": "^1.0.2"
- }
- },
- "is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true
- },
- "is-finalizationregistry": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
- "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3"
- }
- },
- "is-generator-function": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
- "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.0",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- }
- },
- "is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.1"
- }
- },
- "is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
- "dev": true
- },
- "is-number-object": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
- "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- }
- },
- "is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true
- },
- "is-regex": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
- "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- }
- },
- "is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
- "dev": true
- },
- "is-shared-array-buffer": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
- "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3"
- }
- },
- "is-string": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
- "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- }
- },
- "is-symbol": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
- "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "has-symbols": "^1.1.0",
- "safe-regex-test": "^1.1.0"
- }
- },
- "is-typed-array": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
- "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
- "dev": true,
- "requires": {
- "which-typed-array": "^1.1.16"
- }
- },
- "is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
- "dev": true
- },
- "is-weakref": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
- "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3"
- }
- },
- "is-weakset": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
- "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- }
- },
- "isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "iterator.prototype": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
- "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
- "dev": true,
- "requires": {
- "define-data-property": "^1.1.4",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "get-proto": "^1.0.0",
- "has-symbols": "^1.1.0",
- "set-function-name": "^2.0.2"
- }
- },
- "js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
- },
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
- },
- "jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true
- },
- "json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
- "dev": true
- },
- "json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true
- },
- "jsx-ast-utils": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz",
- "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.3",
- "object.assign": "^4.1.2"
- }
- },
- "levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- }
- },
- "lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
- },
- "loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
- "math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "dev": true
- },
- "natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
- "dev": true
- },
- "node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
- },
- "object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true
- },
- "object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true
- },
- "object.assign": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
- "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0",
- "has-symbols": "^1.1.0",
- "object-keys": "^1.1.1"
- }
- },
- "object.entries": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
- "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "object.fromentries": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
- "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0"
- }
- },
- "object.values": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
- "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "requires": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- }
- },
- "own-keys": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
- "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
- "dev": true,
- "requires": {
- "get-intrinsic": "^1.2.6",
- "object-keys": "^1.1.1",
- "safe-push-apply": "^1.0.0"
- }
- },
- "parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "requires": {
- "callsites": "^3.0.0"
- }
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true
- },
- "path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true
- },
- "path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
- },
- "picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true
- },
- "possible-typed-array-names": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
- "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
- "dev": true
- },
- "postcss": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
- "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
- "dev": true,
- "requires": {
- "nanoid": "^3.3.8",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- }
- },
- "prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true
- },
- "prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
- },
- "queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true
- },
- "react": {
- "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==",
- "requires": {
- "loose-envify": "^1.1.0"
- }
- },
- "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==",
- "requires": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
- }
- },
- "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-refresh": {
- "version": "0.14.2",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
- "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
- "dev": true
- },
- "react-router": {
- "version": "6.29.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz",
- "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==",
- "requires": {
- "@remix-run/router": "1.22.0"
- }
- },
- "react-router-dom": {
- "version": "6.29.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz",
- "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==",
- "requires": {
- "@remix-run/router": "1.22.0",
- "react-router": "6.29.0"
- }
- },
- "reflect.getprototypeof": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
- "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.1",
- "which-builtin-type": "^1.2.1"
- }
- },
- "regexp.prototype.flags": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
- "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-errors": "^1.3.0",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "set-function-name": "^2.0.2"
- }
- },
- "resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true
- },
- "reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "rollup": {
- "version": "3.29.5",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
- "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
- "dev": true,
- "requires": {
- "fsevents": "~2.3.2"
- }
- },
- "run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "requires": {
- "queue-microtask": "^1.2.2"
- }
- },
- "safe-array-concat": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
- "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "has-symbols": "^1.1.0",
- "isarray": "^2.0.5"
- },
- "dependencies": {
- "isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true
- }
- }
- },
- "safe-push-apply": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
- "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "isarray": "^2.0.5"
- },
- "dependencies": {
- "isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true
- }
- }
- },
- "safe-regex-test": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
- "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-regex": "^1.2.1"
- }
- },
- "scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "requires": {
- "loose-envify": "^1.1.0"
- }
- },
- "semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true
- },
- "set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dev": true,
- "requires": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- }
- },
- "set-function-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
- "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
- "dev": true,
- "requires": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "functions-have-names": "^1.2.3",
- "has-property-descriptors": "^1.0.2"
- }
- },
- "set-proto": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
- "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
- "dev": true,
- "requires": {
- "dunder-proto": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0"
- }
- },
- "shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "requires": {
- "shebang-regex": "^3.0.0"
- }
- },
- "shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true
- },
- "side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- }
- },
- "side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- }
- },
- "source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true
- },
- "source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true,
- "peer": true
- }
- }
- },
- "string.prototype.matchall": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
- "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "regexp.prototype.flags": "^1.5.3",
- "set-function-name": "^2.0.2",
- "side-channel": "^1.1.0"
- }
- },
- "string.prototype.repeat": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
- "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "string.prototype.trim": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
- "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-object-atoms": "^1.0.0",
- "has-property-descriptors": "^1.0.2"
- }
- },
- "string.prototype.trimend": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
- "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "string.prototype.trimstart": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
- "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true
- },
- "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",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true
- },
- "terser": {
- "version": "5.38.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz",
- "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "dependencies": {
- "commander": {
- "version": "2.20.3",
- "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
- }
- }
- },
- "text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
- "dev": true
- },
- "type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1"
- }
- },
- "type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true
- },
- "typed-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-typed-array": "^1.1.14"
- }
- },
- "typed-array-byte-length": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
- "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.14"
- }
- },
- "typed-array-byte-offset": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
- "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
- "dev": true,
- "requires": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "reflect.getprototypeof": "^1.0.9"
- }
- },
- "typed-array-length": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
- "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.7",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "is-typed-array": "^1.1.13",
- "possible-typed-array-names": "^1.0.0",
- "reflect.getprototypeof": "^1.0.6"
- }
- },
- "unbox-primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
- "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.3",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.1.0",
- "which-boxed-primitive": "^1.1.1"
- }
- },
- "update-browserslist-db": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
- "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
- "dev": true,
- "requires": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- }
- },
- "uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "vite": {
- "version": "4.5.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
- "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
- "dev": true,
- "requires": {
- "esbuild": "^0.18.10",
- "fsevents": "~2.3.2",
- "postcss": "^8.4.27",
- "rollup": "^3.27.1"
- }
- },
- "which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "which-boxed-primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
- "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
- "dev": true,
- "requires": {
- "is-bigint": "^1.1.0",
- "is-boolean-object": "^1.2.1",
- "is-number-object": "^1.1.1",
- "is-string": "^1.1.1",
- "is-symbol": "^1.1.1"
- }
- },
- "which-builtin-type": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
- "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
- "dev": true,
- "requires": {
- "call-bound": "^1.0.2",
- "function.prototype.name": "^1.1.6",
- "has-tostringtag": "^1.0.2",
- "is-async-function": "^2.0.0",
- "is-date-object": "^1.1.0",
- "is-finalizationregistry": "^1.1.0",
- "is-generator-function": "^1.0.10",
- "is-regex": "^1.2.1",
- "is-weakref": "^1.0.2",
- "isarray": "^2.0.5",
- "which-boxed-primitive": "^1.1.0",
- "which-collection": "^1.0.2",
- "which-typed-array": "^1.1.16"
- },
- "dependencies": {
- "isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true
- }
- }
- },
- "which-collection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
- "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
- "dev": true,
- "requires": {
- "is-map": "^2.0.3",
- "is-set": "^2.0.3",
- "is-weakmap": "^2.0.2",
- "is-weakset": "^2.0.3"
- }
- },
- "which-typed-array": {
- "version": "1.1.18",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz",
- "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
- "dev": true,
- "requires": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2"
- }
- },
- "word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
- },
- "yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true
- }
}
}
diff --git a/package.json b/package.json
index 0caab10749..d470a081b3 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,11 @@
]
},
"dependencies": {
+ "lenis": "^1.3.20",
+ "motion": "^12.35.2",
"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-dom": "^18.2.0",
+ "react-router-dom": "^6.18.0"
}
}
diff --git a/public/index.html b/public/index.html
index 9462644fe9..ee89ce356f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1 +1,36 @@
-Hello Rigo with Vanilla.js
\ No newline at end of file
+
+
+
+
+
+ Hello Rigo with Vanilla.js
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/pomify.svg b/public/pomify.svg
new file mode 100644
index 0000000000..81103c1818
--- /dev/null
+++ b/public/pomify.svg
@@ -0,0 +1,7 @@
+
+
+
+
+ P
+
+
\ No newline at end of file
diff --git a/public/pomify_elegido_transparent.png b/public/pomify_elegido_transparent.png
new file mode 100644
index 0000000000..3bebdb1380
Binary files /dev/null and b/public/pomify_elegido_transparent.png differ
diff --git a/public/screenshots/folders-zone.png b/public/screenshots/folders-zone.png
new file mode 100644
index 0000000000..29e205ce85
Binary files /dev/null and b/public/screenshots/folders-zone.png differ
diff --git a/public/screenshots/goals-zone.png b/public/screenshots/goals-zone.png
new file mode 100644
index 0000000000..477c2b3f73
Binary files /dev/null and b/public/screenshots/goals-zone.png differ
diff --git a/public/screenshots/music-zone.png b/public/screenshots/music-zone.png
new file mode 100644
index 0000000000..5052cd398d
Binary files /dev/null and b/public/screenshots/music-zone.png differ
diff --git a/public/screenshots/pages-zone.png b/public/screenshots/pages-zone.png
new file mode 100644
index 0000000000..c0c1f33306
Binary files /dev/null and b/public/screenshots/pages-zone.png differ
diff --git a/public/screenshots/pomodoro-zone.png b/public/screenshots/pomodoro-zone.png
new file mode 100644
index 0000000000..0ef00fbf94
Binary files /dev/null and b/public/screenshots/pomodoro-zone.png differ
diff --git a/render_build.sh b/render_build.sh
index 404821a383..d6ef7c6eea 100644
--- a/render_build.sh
+++ b/render_build.sh
@@ -4,7 +4,7 @@ set -o errexit
npm install
npm run build
-
+pip install pipenv
pipenv install
pipenv run upgrade
diff --git a/requirements.txt b/requirements.txt
index 4eac45f4f8..c49c6a685a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@ sqlalchemy==1.3.23
urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
wtforms==2.3.3
+tomli
\ No newline at end of file
diff --git a/src/api/models.py b/src/api/models.py
index da515f6a1a..71ec65f5af 100644
--- a/src/api/models.py
+++ b/src/api/models.py
@@ -1,19 +1,120 @@
from flask_sqlalchemy import SQLAlchemy
-from sqlalchemy import String, Boolean
-from sqlalchemy.orm import Mapped, mapped_column
+from sqlalchemy import String, Boolean, Text, DateTime, ForeignKey
+from sqlalchemy.orm import Mapped, mapped_column, relationship
+from datetime import datetime, timezone
+from typing import List
+from flask_bcrypt import generate_password_hash, check_password_hash
db = SQLAlchemy()
+
class User(db.Model):
+ __tablename__ = "user_table"
+
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)
+ name: Mapped[str] = mapped_column(String(120), nullable=True)
+ email: Mapped[str] = mapped_column(
+ String(120), unique=True, nullable=False)
+ password_hash: Mapped[str] = mapped_column(nullable=False)
+ avatar_url: Mapped[str] = mapped_column(String(500), unique=True, nullable=True)
+ reset_token: Mapped[str] = mapped_column(String(500), nullable=True)
+
+ def set_password(self, password):
+ self.password_hash = generate_password_hash(password).decode('utf-8')
+ def check_password(self, password):
+ return check_password_hash(self.password_hash, password)
+ # cascade-delete permite que, al borrar user, se borren todos los folders, lo mismo con goals.leer más en la documetacion,
+ folders: Mapped[List["Folder"]] = relationship(
+ back_populates="user", cascade="all, delete-orphan")
+
+ goals: Mapped[List["Goals"]] = relationship(
+ back_populates="user", cascade="all, delete-orphan")
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
+ }
+
+
+class Folder(db.Model):
+ __tablename__ = "folder_table"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ user_id: Mapped[int] = mapped_column(
+ ForeignKey("user_table.id"), nullable=False)
+ user: Mapped["User"] = relationship(back_populates="folders")
+
+ pages: Mapped[List["Page"]] = relationship(
+ back_populates="folder", cascade="all, delete-orphan")
+
+ title: Mapped[str] = mapped_column(String(120), nullable=False)
+ created_at: Mapped[datetime] = mapped_column(
+ DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "title": self.title,
+ "pages": [page.serialize() for page in self.pages],
+ "created_at": self.created_at
+ }
+
+
+class Page(db.Model):
+ __tablename__ = "page_table"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+ title: Mapped[str] = mapped_column(
+ String(120), nullable=False)
+ content: Mapped[str] = mapped_column(Text, nullable=False)
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
+ update_at: Mapped[datetime] = mapped_column(DateTime(timezone=True),default=lambda: datetime.now(timezone.utc),onupdate=lambda: datetime.now(timezone.utc)
+)
+
+ folder_id: Mapped[int] = mapped_column(
+ ForeignKey("folder_table.id"), nullable=False)
+ folder: Mapped["Folder"] = relationship(back_populates="pages")
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "title": self.title,
+ "content": self.content,
+ "folder": {
+ "id": self.folder.id,
+ "title": self.folder.title
+ },
+ "created_at": self.created_at.isoformat(),
+ "update_at": self.update_at.isoformat()
+ }
+
+
+class Goals(db.Model):
+ __tablename__ = "goal_table"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+ title: Mapped[str] = mapped_column(
+ String(120), unique=True, nullable=False)
+ content: Mapped[str] = mapped_column(Text, nullable=False)
+ status: Mapped[str] = mapped_column(
+ String(20), nullable=False, default="not_started")
+ created_at: Mapped[datetime] = mapped_column(
+ DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
+
+ user_id: Mapped[int] = mapped_column(
+ ForeignKey("user_table.id"), nullable=False)
+ user: Mapped["User"] = relationship(back_populates="goals")
+
+ def serialize(self):
+ return {
+ "id": self.id,
+ "title": self.title,
+ "content": self.content,
+ "status": self.status,
+ "created_at": self.created_at
+ }
diff --git a/src/api/routes.py b/src/api/routes.py
index 029589a3a1..d91474cde4 100644
--- a/src/api/routes.py
+++ b/src/api/routes.py
@@ -2,21 +2,15 @@
This module takes care of starting the API Server, Loading the DB and Adding the endpoints
"""
from flask import Flask, request, jsonify, url_for, Blueprint
-from api.models import db, User
+from api.models import db, User, Folder, Page, Goals
from api.utils import generate_sitemap, APIException
from flask_cors import CORS
api = Blueprint('api', __name__)
-# Allow CORS requests to this API
CORS(api)
@api.route('/hello', methods=['POST', 'GET'])
def handle_hello():
-
- response_body = {
- "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request"
- }
-
- return jsonify(response_body), 200
+ return jsonify({"message": "Hello! I'm a message that came from the backend"}), 200
diff --git a/src/api/routes/__init__.py b/src/api/routes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/api/routes/folder.py b/src/api/routes/folder.py
new file mode 100644
index 0000000000..1777fa6030
--- /dev/null
+++ b/src/api/routes/folder.py
@@ -0,0 +1,99 @@
+from flask import Flask, request, jsonify, url_for, Blueprint
+from api.models import db, User, Folder, Page
+from api.utils import generate_sitemap, APIException
+from flask_cors import CORS
+from flask_jwt_extended import jwt_required, get_jwt_identity
+
+folders_bp = Blueprint("folders_bp", __name__)
+
+@folders_bp.route('/', methods=['GET', 'POST'])
+@jwt_required()
+def folders():
+ current_user_id = get_jwt_identity()
+
+ if request.method == 'GET':
+ folders = Folder.query.filter_by(user_id=int(current_user_id)).all()
+ return jsonify([folder.serialize() for folder in folders]), 200
+
+ if request.method == 'POST':
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ title = data.get("title")
+ if not title:
+ return jsonify({"error": "title is required"}), 400
+ new_folder = Folder(title=title, user_id=int(current_user_id))
+ db.session.add(new_folder)
+ db.session.commit()
+ return jsonify(new_folder.serialize()), 201
+
+@folders_bp.route('/', methods=['GET'])
+@jwt_required()
+def get_folder(id):
+ current_user_id = get_jwt_identity()
+ folder = Folder.query.get(id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ return jsonify(folder.serialize()), 200
+
+@folders_bp.route('/', methods=['PUT'])
+@jwt_required()
+def update_folder(id):
+ current_user_id = get_jwt_identity()
+ folder = Folder.query.get(id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ if "title" in data:
+ folder.title = data["title"]
+ db.session.commit()
+ return jsonify({
+ "message": "Folder updated successfully",
+ "folder": folder.serialize()
+ }), 200
+
+@folders_bp.route('/', methods=['DELETE'])
+@jwt_required()
+def delete_folder(id):
+ current_user_id = get_jwt_identity()
+ folder = Folder.query.get(id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ db.session.delete(folder)
+ db.session.commit()
+ return jsonify({"message": f"Folder {id} eliminado"}), 200
+
+@folders_bp.route('//pages', methods=['GET', 'POST'])
+@jwt_required()
+def folder_pages(folder_id):
+ current_user_id = get_jwt_identity()
+ folder = Folder.query.get(folder_id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+
+ if request.method == 'GET':
+ pages = Page.query.filter_by(folder_id=folder_id).all()
+ return jsonify([page.serialize() for page in pages]), 200
+
+ if request.method == 'POST':
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ title = data.get("title")
+ if not title:
+ return jsonify({"error": "title is required"}), 400
+ content = data.get("content", "")
+ new_page = Page(title=title, content=content, folder_id=folder_id)
+ db.session.add(new_page)
+ db.session.commit()
+ return jsonify(new_page.serialize()), 201
\ No newline at end of file
diff --git a/src/api/routes/goals.py b/src/api/routes/goals.py
new file mode 100644
index 0000000000..9ff7f78641
--- /dev/null
+++ b/src/api/routes/goals.py
@@ -0,0 +1,116 @@
+from flask import Flask, request, jsonify, url_for, Blueprint
+from api.models import db, User, Goals
+from api.utils import generate_sitemap, APIException
+from flask_cors import CORS
+from flask_jwt_extended import jwt_required, get_jwt_identity
+
+goals_bp = Blueprint("goals_bp", __name__)
+
+
+
+@goals_bp.route('/', methods=['GET'])
+@jwt_required()
+def get_goals():
+
+ current_user_id = get_jwt_identity()
+
+ goals = Goals.query.filter_by(user_id=current_user_id).all()
+ response = [goal.serialize() for goal in goals]
+ return jsonify(response), 200
+
+
+@goals_bp.route('/', methods=['GET'])
+@jwt_required()
+def get_single_goal(goal_id):
+
+ current_user_id = get_jwt_identity()
+
+ goal = Goals.query.filter_by(id=goal_id, user_id=current_user_id).first()
+ if not goal:
+ return jsonify({"error": "goal no exist"}), 404
+ return jsonify(goal.serialize()), 200
+
+@goals_bp.route('/', methods=['POST'])
+@jwt_required()
+def create_goal():
+
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ title = data.get("title")
+ content = data.get("content")
+ if not title or not content:
+ return jsonify({"error": "title and content are required"}), 400
+
+ current_user_id = get_jwt_identity()
+
+ new_goal = Goals(
+ title=title,
+ content=content,
+ user_id=current_user_id
+ )
+ db.session.add(new_goal)
+ db.session.commit()
+ return jsonify({"msg": "Goal created successfully", "goal": new_goal.serialize()}), 201
+
+@goals_bp.route('/', methods=['PUT'])
+@jwt_required()
+def update_goal(goal_id):
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ current_user_id = get_jwt_identity()
+
+ goal = Goals.query.filter_by(id=goal_id, user_id=current_user_id).first()
+
+ if not goal:
+ return jsonify({"error": "Goal not found"}), 404
+
+ if "title" in data:
+ goal.title = data["title"]
+
+ if "content" in data:
+ goal.content = data["content"]
+
+ if "status" in data:
+ goal.status = data["status"]
+
+ db.session.commit()
+
+ return jsonify({
+ "message": "Goal updated successfully",
+ "goal": goal.serialize()
+ }), 200
+
+@goals_bp.route('/', methods=['DELETE'])
+@jwt_required()
+def delete_goal(goal_id):
+
+ current_user_id = get_jwt_identity()
+ goal = Goals.query.filter_by(id=goal_id, user_id=current_user_id).first()
+ if not goal:
+ return jsonify({"error": "Goal not found"}), 404
+
+ db.session.delete(goal)
+ db.session.commit()
+
+ return jsonify({
+ "message": "Goal deleted successfully"}), 200
+
+@goals_bp.route('/', methods=['DELETE'])
+@jwt_required()
+def delete_all_goals():
+ current_user_id = get_jwt_identity()
+ goals = Goals.query.filter_by(user_id=current_user_id).all()
+
+ if not goals:
+ return jsonify({"message": "No goals found for this user"}), 404
+
+ for goal in goals:
+ db.session.delete(goal)
+
+ db.session.commit()
+
+ return jsonify({
+ "message": f"{len(goals)} goals deleted successfully"
+ }), 200
\ No newline at end of file
diff --git a/src/api/routes/page.py b/src/api/routes/page.py
new file mode 100644
index 0000000000..b98149c861
--- /dev/null
+++ b/src/api/routes/page.py
@@ -0,0 +1,61 @@
+from flask import Flask, request, jsonify, url_for, Blueprint
+from api.models import db, Folder, Page
+from api.utils import generate_sitemap, APIException
+from flask_cors import CORS
+from flask_jwt_extended import jwt_required, get_jwt_identity
+
+pages_bp = Blueprint("pages_bp", __name__)
+
+@pages_bp.route('/', methods=['GET'])
+@jwt_required()
+def get_page(id):
+ current_user_id = get_jwt_identity()
+ page = Page.query.get(id)
+ if not page:
+ return jsonify({"error": "Page no encontrada"}), 404
+ folder = Folder.query.get(page.folder_id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ return jsonify(page.serialize()), 200
+
+@pages_bp.route('/', methods=['PUT'])
+@jwt_required()
+def update_page(id):
+ current_user_id = get_jwt_identity()
+ page = Page.query.get(id)
+ if not page:
+ return jsonify({"error": "Page no encontrada"}), 404
+ folder = Folder.query.get(page.folder_id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ data = request.get_json()
+ if not data:
+ return jsonify({"error": "Request body is required"}), 400
+ title = data.get("title")
+ content = data.get("content")
+ if not title or not content:
+ return jsonify({"error": "title and content are required"}), 400
+ page.title = title
+ page.content = content
+ db.session.commit()
+ return jsonify(page.serialize()), 200
+
+@pages_bp.route('/', methods=['DELETE'])
+@jwt_required()
+def delete_page(id):
+ current_user_id = get_jwt_identity()
+ page = Page.query.get(id)
+ if not page:
+ return jsonify({"error": "Page no encontrada"}), 404
+ folder = Folder.query.get(page.folder_id)
+ if not folder:
+ return jsonify({"error": "Folder no encontrado"}), 404
+ if folder.user_id != int(current_user_id):
+ return jsonify({"error": "No autorizado"}), 403
+ db.session.delete(page)
+ db.session.commit()
+ return jsonify({"message": f"Page {id} eliminada"}), 200
\ No newline at end of file
diff --git a/src/api/routes/user_routes.py b/src/api/routes/user_routes.py
new file mode 100644
index 0000000000..1c3e362fc5
--- /dev/null
+++ b/src/api/routes/user_routes.py
@@ -0,0 +1,198 @@
+from flask import Flask, request, jsonify, Blueprint
+from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
+from api.models import db, User
+import secrets
+import resend
+import os
+
+user_routes_bp = Blueprint('user_routes_bp', __name__)
+
+#LOGIN:
+@user_routes_bp.route('/login', methods=['POST'])
+def login():
+ data = request.get_json()
+ email = data.get('email')
+ password = data.get('password')
+
+ if not email or not password:
+ return jsonify({"message": "Email and password are required"}), 400
+
+ user = db.session.execute(db.select(User).where(
+ User.email == email)).scalar_one_or_none()
+
+ if user is None:
+ return jsonify({"message": "Invalid email or password"}), 400
+
+ if user.check_password(password):
+ access_token = create_access_token(identity=str(user.id))
+ return jsonify({"message": "Login successful", "token": access_token}), 200
+ else:
+ return jsonify({"message": "Invalid email or password"}), 400
+
+#REGISTER:
+@user_routes_bp.route('/register', methods=['POST'])
+def register():
+ data = request.get_json()
+ print("DATA:", data)
+ name = data.get('name')
+ email = data.get('email')
+ password = data.get('password')
+
+ if not email or not password or not name:
+ return jsonify({"message": "Email, password and name are required"}), 400
+
+ existing_user = db.session.execute(db.select(User).where(
+ User.email == email)).scalar_one_or_none()
+
+ if existing_user:
+ return jsonify({"message": "user already registered"}), 400
+
+ new_user = User(name=name, email=email)
+ new_user.set_password(password)
+
+ db.session.add(new_user)
+ db.session.commit()
+
+ return jsonify({"message": "User registered successfully"}), 201
+
+#OBTENER TODOS LOS USUARIOS
+@user_routes_bp.route('/', methods=['GET'])
+def get_users():
+ users = User.query.all()
+ users_list = [{"id": user.id, "email": user.email, "name": user.name,
+ "avatar_url": user.avatar_url} for user in users]
+ return jsonify(users_list), 200
+
+#OBTENER UN USUARIO(CON SU TOKEN)
+@user_routes_bp.route('/profile', methods=['GET'])
+@jwt_required()
+def get_profile():
+ user_id = get_jwt_identity()
+ user = db.session.get(User, int(user_id))
+ if not user:
+ return jsonify({"message": "User not found"}), 404
+ return jsonify(user.serialize()), 200
+
+#PUT solo el usuario logeado puede actualizar su perfil.
+@user_routes_bp.route('/profile', methods=['PUT'])
+@jwt_required()
+def update_user():
+
+ current_user_id = get_jwt_identity()
+ user = db.session.get(User, int(current_user_id))
+
+ if not user:
+ return jsonify({"message": "User not found"}), 404
+
+ data = request.get_json()
+
+ if "name" in data:
+ user.name = data["name"]
+
+ if "email" in data:
+ user.email = data["email"]
+
+ if "avatar_url" in data:
+ user.avatar_url = data["avatar_url"]
+
+ if "password" in data:
+ user.set_password(data["password"])
+
+ db.session.commit()
+
+ return jsonify({
+ "message": "User updated successfully",
+ "user": user.serialize()
+ }), 200
+
+#DELETE solo el usuario logeado puede eliminar su perfil.
+
+@user_routes_bp.route('/profile', methods=['DELETE'])
+@jwt_required()
+def delete_user():
+
+ current_user_id = get_jwt_identity()
+ user = db.session.get(User, int(current_user_id))
+
+ if not user:
+ return jsonify({"message": "User not found"}), 404
+
+ db.session.delete(user)
+ db.session.commit()
+
+ return jsonify({"message": "User deleted successfully"}), 200
+
+
+
+#recuperacion de contraseña
+
+@user_routes_bp.route('/forgot-password', methods=['POST'])
+def forgot_password():
+ data = request.get_json()
+ email = data.get('email', '').lower()
+ if not email:
+ return jsonify({"message": "Email is required"}), 400
+
+ user = db.session.execute(
+ db.select(User).where(User.email == email)
+ ).scalar_one_or_none()
+
+ if user is None:
+ return jsonify({"message": "User not found"}), 404
+
+ token = create_access_token(identity=str(user.id))
+ user.reset_token = token
+ db.session.commit()
+
+
+ resend.api_key = os.getenv("RESEND_API_KEY")
+
+ resend.Emails.send({
+ "from": "onboarding@resend.dev",
+ "to": [email],
+ "subject": "Reset your password - Pomify",
+ "html": f"""
+ Reset your password
+ Click the link below to reset your password. It expires in 1 hour.
+
+ Reset password
+
+ """
+ })
+
+ return jsonify({"message": "Password reset email sent"}), 200
+
+
+@user_routes_bp.route('/reset-password', methods=['POST'])
+def reset_password():
+ data = request.get_json()
+ token = data.get('token')
+ new_password = data.get('password')
+
+ if not token or not new_password:
+ return jsonify({"message": "Token and password are required"}), 400
+
+ user = db.session.execute(
+ db.select(User).where(User.reset_token == token)
+ ).scalar_one_or_none()
+
+ if user is None:
+ return jsonify({"message": "Invalid or expired token"}), 404
+
+ user.set_password(new_password)
+ user.reset_token = None
+ db.session.commit()
+
+ return jsonify({"message": "Password updated successfully"}), 200
+
+
+
+# codigo de la academia:
+@user_routes_bp.route('/hello', methods=['POST', 'GET'])
+def handle_hello():
+
+ response_body = {
+ "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request"
+ }
+
+ return jsonify(response_body), 200
\ No newline at end of file
diff --git a/src/app.py b/src/app.py
index 1b3340c0fa..d507148ea4 100644
--- a/src/app.py
+++ b/src/app.py
@@ -1,25 +1,32 @@
-"""
-This module takes care of starting the API Server, Loading the DB and Adding the endpoints
-"""
import os
from flask import Flask, request, jsonify, url_for, send_from_directory
from flask_migrate import Migrate
from flask_swagger import swagger
+from flask_cors import CORS
from api.utils import APIException, generate_sitemap
from api.models import db
-from api.routes import api
+from api.routes.user_routes import user_routes_bp
+from api.routes.folder import folders_bp
+from api.routes.page import pages_bp
+from api.routes.goals import goals_bp
from api.admin import setup_admin
from api.commands import setup_commands
-
-# from models import Person
+from flask_jwt_extended import JWTManager
+from datetime import timedelta
+from flask_mail import Mail
ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production"
static_file_dir = os.path.join(os.path.dirname(
os.path.realpath(__file__)), '../dist/')
app = Flask(__name__)
app.url_map.strict_slashes = False
+CORS(app, resources={r"/api/*": {"origins": "*"}})
+mail = Mail(app)
+
+app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY")
+jwt = JWTManager(app)
+app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(days=2)
-# database condiguration
db_url = os.getenv("DATABASE_URL")
if db_url is not None:
app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace(
@@ -31,42 +38,32 @@
MIGRATE = Migrate(app, db, compare_type=True)
db.init_app(app)
-# add the admin
setup_admin(app)
-
-# add the admin
setup_commands(app)
-# Add all endpoints form the API with a "api" prefix
-app.register_blueprint(api, url_prefix='/api')
-
-# Handle/serialize errors like a JSON object
-
+app.register_blueprint(user_routes_bp, url_prefix='/api/user')
+app.register_blueprint(folders_bp, url_prefix='/api/folders')
+app.register_blueprint(pages_bp, url_prefix='/api/pages')
+app.register_blueprint(goals_bp, url_prefix='/api/goals')
@app.errorhandler(APIException)
def handle_invalid_usage(error):
return jsonify(error.to_dict()), error.status_code
-# generate sitemap with all your endpoints
-
-
@app.route('/')
def sitemap():
if ENV == "development":
return generate_sitemap(app)
return send_from_directory(static_file_dir, 'index.html')
-# any other endpoint will try to serve it like a static file
@app.route('/', methods=['GET'])
def serve_any_other_file(path):
if not os.path.isfile(os.path.join(static_file_dir, path)):
path = 'index.html'
response = send_from_directory(static_file_dir, path)
- response.cache_control.max_age = 0 # avoid cache memory
+ response.cache_control.max_age = 0
return response
-
-# this only runs if `$ python src/main.py` is executed
if __name__ == '__main__':
PORT = int(os.environ.get('PORT', 3001))
- app.run(host='0.0.0.0', port=PORT, debug=True)
+ app.run(host='0.0.0.0', port=PORT, debug=True)
\ No newline at end of file
diff --git a/src/front/components/BreakModal.jsx b/src/front/components/BreakModal.jsx
new file mode 100644
index 0000000000..12cb00c851
--- /dev/null
+++ b/src/front/components/BreakModal.jsx
@@ -0,0 +1,96 @@
+import { useEffect, useState } from "react";
+import "../styles/breakModal.css";
+
+const TIPS = {
+ 5: [
+ "Shake your hands and relax your wrists.",
+ "Close your eyes and take three deep breaths.",
+ "Look out the window or focus on a distant point to rest your eyes.",
+ "Roll your shoulders back and stretch your neck — 30 seconds each side.",
+ "Stand up, take a couple of steps, and sit back down.",
+ ],
+ 10: [
+ "Take a short walk — any distance counts.",
+ "Get a glass of water or make a warm drink.",
+ "Do 5 minutes of stretching: back, legs, arms.",
+ "Listen to a song you like and do nothing else.",
+ "Close your laptop for a moment and let your eyes rest.",
+ ],
+ 15: [
+ "Step outside for a minute — natural light recharges you.",
+ "Have a light snack and drink some water.",
+ "Go for a short 5–10 minute walk.",
+ "Write on paper what you want to accomplish in the next block.",
+ "Try box breathing: inhale 4s, hold 4s, exhale 4s.",
+ ],
+};
+
+const formatTime = (s) =>
+ `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;
+
+const getTipsForPreset = (breakSeconds) => {
+ if (breakSeconds <= 5 * 60) return TIPS[5];
+ if (breakSeconds <= 10 * 60) return TIPS[10];
+ return TIPS[15];
+};
+
+export const BreakModal = ({ timeLeft, presetBreak, onSkip, onBreakEnd }) => {
+ const [tip] = useState(() => {
+ const tips = getTipsForPreset(presetBreak);
+ return tips[Math.floor(Math.random() * tips.length)];
+ });
+ const [showConfirm, setShowConfirm] = useState(false);
+
+ useEffect(() => {
+ if (timeLeft === 0) {
+ onBreakEnd();
+ }
+ }, [timeLeft, onBreakEnd]);
+
+ return (
+
+
+ {!showConfirm ? (
+ <>
+
setShowConfirm(true)}
+ aria-label="Close modal"
+ >
+ ✕
+
+
+
break time
+
{formatTime(timeLeft)}
+
+
+
+
{tip}
+ >
+ ) : (
+
+
+ We recommend not skipping your breaks.
+
+ Are you sure you want to continue your pomodoro?
+
+
+ onSkip(timeLeft)}
+ >
+ yes, continue
+
+ setShowConfirm(false)}
+ >
+ no, keep resting
+
+
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/Folder.jsx b/src/front/components/Folder.jsx
new file mode 100644
index 0000000000..87708d5dbc
--- /dev/null
+++ b/src/front/components/Folder.jsx
@@ -0,0 +1,71 @@
+import { useState } from 'react';
+import '../styles/Folder.css';
+
+const darkenColor = (hex, percent) => {
+ let color = hex.startsWith('#') ? hex.slice(1) : hex;
+ if (color.length === 3) color = color.split('').map(c => c + c).join('');
+ const num = parseInt(color, 16);
+ let r = Math.max(0, Math.min(255, Math.floor(((num >> 16) & 0xff) * (1 - percent))));
+ let g = Math.max(0, Math.min(255, Math.floor(((num >> 8) & 0xff) * (1 - percent))));
+ let b = Math.max(0, Math.min(255, Math.floor((num & 0xff) * (1 - percent))));
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
+};
+
+const Folder = ({ color = '#2D3A4A', size = 1, items = [], className = '' }) => {
+ const maxItems = 3;
+ const papers = items.slice(0, maxItems);
+ while (papers.length < maxItems) papers.push(null);
+
+ const [open, setOpen] = useState(false);
+ const [paperOffsets, setPaperOffsets] = useState(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));
+
+ const folderBackColor = darkenColor(color, 0.08);
+ const paper1 = darkenColor('#ffffff', 0.1);
+ const paper2 = darkenColor('#ffffff', 0.05);
+ const paper3 = '#ffffff';
+
+ const handleClick = () => {
+ setOpen(prev => !prev);
+ if (open) setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));
+ };
+
+ const handlePaperMouseMove = (e, index) => {
+ if (!open) return;
+ const rect = e.currentTarget.getBoundingClientRect();
+ const offsetX = (e.clientX - rect.left - rect.width / 2) * 0.15;
+ const offsetY = (e.clientY - rect.top - rect.height / 2) * 0.15;
+ setPaperOffsets(prev => { const n = [...prev]; n[index] = { x: offsetX, y: offsetY }; return n; });
+ };
+
+ const handlePaperMouseLeave = (e, index) => {
+ setPaperOffsets(prev => { const n = [...prev]; n[index] = { x: 0, y: 0 }; return n; });
+ };
+
+ return (
+
+
+
+ {papers.map((item, i) => (
+
handlePaperMouseMove(e, i)}
+ onMouseLeave={e => handlePaperMouseLeave(e, i)}
+ style={open ? { '--magnet-x': `${paperOffsets[i]?.x || 0}px`, '--magnet-y': `${paperOffsets[i]?.y || 0}px` } : {}}
+ >
+ {item}
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default Folder;
\ No newline at end of file
diff --git a/src/front/components/Footer.jsx b/src/front/components/Footer.jsx
index f06302dbd2..8d127706a2 100644
--- a/src/front/components/Footer.jsx
+++ b/src/front/components/Footer.jsx
@@ -1,11 +1,12 @@
-export const Footer = () => (
-
-);
+import "../styles/footer.css";
+
+export const Footer = () => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/GlobalAudioPlayer.jsx b/src/front/components/GlobalAudioPlayer.jsx
new file mode 100644
index 0000000000..c3b1c47e74
--- /dev/null
+++ b/src/front/components/GlobalAudioPlayer.jsx
@@ -0,0 +1,38 @@
+import useGlobalReducer from "../hooks/useGlobalReducer";
+
+export const GlobalAudioPlayer = () => {
+ const { store, dispatch, audioRef } = useGlobalReducer();
+ const { currentPlaylist, currentTrackIndex, isPlaying } = store;
+
+ const playNext = () => {
+ if (!currentPlaylist || currentTrackIndex >= currentPlaylist.sounds.length - 1) return;
+ dispatch({ type: "set_track_index", payload: currentTrackIndex + 1 });
+ };
+
+ const playPrevious = () => {
+ if (!currentPlaylist || currentTrackIndex <= 0) return;
+ dispatch({ type: "set_track_index", payload: currentTrackIndex - 1 });
+ };
+
+ if (!currentPlaylist) return null;
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/LoginModal.jsx b/src/front/components/LoginModal.jsx
new file mode 100644
index 0000000000..7391797307
--- /dev/null
+++ b/src/front/components/LoginModal.jsx
@@ -0,0 +1,158 @@
+import "../styles/loginModal.css";
+import { useState } from "react";
+import { loginUser, forgotPassword } from "../services/loginBS";
+import { useNavigate } from "react-router-dom";
+
+export const LoginModal = ({ onClose, onSwitchToRegister }) => {
+
+ const [user, setUser] = useState({ email: "", password: "" });
+ const [error, setError] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
+ const [view, setView] = useState("login");
+ const [forgotEmail, setForgotEmail] = useState("");
+ const [forgotMessage, setForgotMessage] = useState("");
+ const navigate = useNavigate();
+
+ const handleOverlayClick = (e) => {
+ if (e.target === e.currentTarget) onClose();
+ };
+
+ const handleChange = (e) => {
+ setUser({ ...user, [e.target.name]: e.target.value });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (!user.email || !user.password) {
+ setError("All fields are required.");
+ return;
+ }
+ try {
+ const data = await loginUser({ ...user, email: user.email.trim().toLowerCase() });
+ localStorage.setItem("token", data.token);
+ onClose();
+ navigate("/home");
+ } catch (err) {
+ setError(err.message);
+ }
+ };
+
+ const handleForgot = async (e) => {
+ e.preventDefault();
+ if (!forgotEmail) { setForgotMessage("Please enter your email."); return; }
+ try {
+ await forgotPassword(forgotEmail.trim().toLowerCase());
+ setForgotMessage("Check your email for the reset link!");
+ } catch (err) {
+ setForgotMessage(err.message);
+ }
+ };
+
+ return (
+
+
+
+
✕
+
+ {view === "login" ? (
+ <>
+
log in
+
+
+
+
+ Don't have an account?{" "}
+
+ Sign up
+
+
+ >
+ ) : (
+ <>
+
reset password
+
+
+ Enter your email to receive a reset link.
+
+
+
+
+
+ setView("login")}>
+ ← Back to log in
+
+ {" · "}
+
+ Sign up
+
+
+ >
+ )}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx
index 30d43a2636..b4738e036b 100644
--- a/src/front/components/Navbar.jsx
+++ b/src/front/components/Navbar.jsx
@@ -1,19 +1,282 @@
-import { Link } from "react-router-dom";
+import { Link, useLocation, useNavigate } from "react-router-dom";
+import "../styles/Navbar.css";
+import { useState, useEffect } from "react";
+import { LoginModal } from "./LoginModal";
+import { RegisterModal } from "./RegisterModal";
+import { getGoals, updateGoal, deleteGoal, createGoal } from "./goals/GoalsService";
+
+const backend_url = import.meta.env.VITE_BACKEND_URL;
export const Navbar = () => {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const [showLogin, setShowLogin] = useState(false);
+ const [showRegister, setShowRegister] = useState(false);
+ const [user, setUser] = useState(null);
+
+ const [showGoalsModal, setShowGoalsModal] = useState(false);
+ const [goals, setGoals] = useState([]);
+ const [editingId, setEditingId] = useState(null);
+ const [editingText, setEditingText] = useState("");
+ const [newGoalText, setNewGoalText] = useState("");
+
+ useEffect(() => {
+ const token = localStorage.getItem("token");
+ setIsLoggedIn(!!token);
+ if (token) fetchUser();
+ }, [location]);
+
+ useEffect(() => {
+ const handleClickOutside = (e) => {
+ if (!e.target.closest(".user-dropdown-container")) {
+ setIsDropdownOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ const fetchUser = async () => {
+ const res = await fetch(`${backend_url}/api/user/profile`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!res.ok) return;
+ const data = await res.json();
+ setUser(data);
+ };
+
+ const fetchGoals = async () => {
+ const data = await getGoals();
+ if (data) setGoals(data.filter(g => g));
+ };
+
+ const handleLogout = () => {
+ localStorage.removeItem("token");
+ setIsLoggedIn(false);
+ setUser(null);
+ window.location.href = "/";
+ };
+
+ const openGoalsModal = () => {
+ fetchGoals();
+ setShowGoalsModal(true);
+ };
+
+ const saveEdit = async (id) => {
+ if (!editingText.trim()) return;
+ const data = await updateGoal(id, { title: editingText });
+ if (!data?.goal) return;
+ setGoals(goals.map(g => g.id === id ? data.goal : g));
+ setEditingId(null);
+ setEditingText("");
+ };
+
+ const handleDelete = async (id) => {
+ await deleteGoal(id);
+ setGoals(goals.filter(g => g.id !== id));
+ if (selectedGoal === id) setSelectedGoal(null);
+ };
+
+ const handleCreateGoal = async () => {
+ if (!newGoalText.trim()) return;
+ const data = await createGoal(newGoalText.trim(), newGoalText.trim());
+ if (!data?.goal) return;
+ setGoals([...goals, data.goal]);
+ setNewGoalText("");
+ window.dispatchEvent(new Event("goals:updated"));
+ };
+
+ const handleCreateGoalAndGo = async () => {
+ if (newGoalText.trim()) {
+ await handleCreateGoal();
+ }
+ setShowGoalsModal(false);
+ navigate("/goals");
+ };
+
+ const changeStatus = async (id, status) => {
+ const data = await updateGoal(id, { status });
+ if (!data?.goal) return;
+ setGoals(goals.map(g => g.id === id ? data.goal : g));
+ };
+
+ const getInitial = () => {
+ if (!user) return "U";
+ return (user.name || user.email || "U")[0].toUpperCase();
+ };
+
+ const renderAvatar = () => {
+ if (!user?.avatar_url) return getInitial();
+ if (user.avatar_url.startsWith("http")) {
+ return ;
+ }
+ return {user.avatar_url} ;
+ };
+
+ const statusColors = {
+ urgent: { bg: "rgba(230,57,70,0.15)", color: "#e63946", active: "#e63946" },
+ progress: { bg: "rgba(168,218,220,0.3)", color: "#457b9d", active: "#457b9d" },
+ done: { bg: "rgba(69,123,157,0.15)", color: "#457b9d", active: "#457b9d" }
+ };
+
+ return (
+ <>
+
+
+
+
+
navigate("/about")} style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "6px" }}>
+ i
+ POMIFY
+
+
+
+ {isLoggedIn && (
+
+ Your Goals
+
+ )}
+
+
+
+ {isLoggedIn ? (
+
+
setIsDropdownOpen(!isDropdownOpen)}
+ >
+ {user?.name || "Usuario"}
+
+ {renderAvatar()}
+
+
+
+ {isDropdownOpen && (
+
+ setIsDropdownOpen(false)}>
+ Edit profile
+
+ setIsDropdownOpen(false)}>
+ My folders
+
+ Logout
+
+ )}
+
+ ) : (
+
+ setShowRegister(true)}>Register
+ setShowLogin(true)}>Login
+
+ )}
+
+
+
+
+
+ {showGoalsModal && (
+ setShowGoalsModal(false)}>
+
e.stopPropagation()}>
+
+
+
Your Goals
+ setShowGoalsModal(false)}>✕
+
+
+
+ {goals.length === 0 && (
+
+
+ No goals yet. Create one!
+
+
+ setNewGoalText(e.target.value)}
+ onKeyDown={e => e.key === "Enter" && handleCreateGoal()}
+ autoFocus
+ />
+
+ + Create
+
+
+
+ )}
+ {goals.map(goal => (
+
+
+ {editingId === goal.id ? (
+
setEditingText(e.target.value)}
+ onBlur={() => saveEdit(goal.id)}
+ onKeyDown={e => e.key === "Enter" && saveEdit(goal.id)}
+ autoFocus
+ />
+ ) : (
+
{ setEditingId(goal.id); setEditingText(goal.title); }}>
+ {goal.title}
+
+ )}
+
+ { setEditingId(goal.id); setEditingText(goal.title); }}>✏
+ handleDelete(goal.id)}>🗑
+
+
+
+
+ {["urgent", "progress", "done"].map(s => (
+ changeStatus(goal.id, s)}
+ style={{
+ flex: 1,
+ padding: "5px 0",
+ borderRadius: "25px",
+ border: "none",
+ cursor: "pointer",
+ fontSize: "0.72rem",
+ whiteSpace: "nowrap",
+ transition: "all 0.2s ease",
+ background: goal.status === s ? statusColors[s].active : statusColors[s].bg,
+ color: goal.status === s ? "white" : statusColors[s].color,
+ height: "auto",
+ overflow: "visible",
+ display: "inline-block",
+ lineHeight: "1.5"
+ }}
+ >
+ {s === "urgent" ? "Urgent" : s === "progress" ? "In Progress" : "Done"}
+
+ ))}
+
+
+ ))}
+
+
+
+ { setShowGoalsModal(false); navigate("/goals"); }}>
+ {goals.length === 0 ? "Go to Goals" : "View All"}
+
+
+
+
+
+ )}
+
+ {showLogin && (
+ setShowLogin(false)} onSwitchToRegister={() => { setShowLogin(false); setShowRegister(true); }} />
+ )}
+ {showRegister && (
+ setShowRegister(false)} onSwitchToLogin={() => { setShowRegister(false); setShowLogin(true); }} />
+ )}
- return (
-
-
-
-
React Boilerplate
-
-
-
- Check the Context in action
-
-
-
-
- );
+ >
+ );
};
\ No newline at end of file
diff --git a/src/front/components/PagesZone.jsx b/src/front/components/PagesZone.jsx
new file mode 100644
index 0000000000..e79e4d777d
--- /dev/null
+++ b/src/front/components/PagesZone.jsx
@@ -0,0 +1,374 @@
+import { useState, useEffect, useRef } from "react";
+import { useNavigate } from "react-router-dom";
+import { getFolders, createFolder } from "./pages-y-folder/FolderServices";
+import { getPages, createPage } from "./pages-y-folder/PageServices";
+import "../styles/pagesZone.css";
+
+export const PagesZone = () => {
+ const [title, setTitle] = useState("");
+ const [content, setContent] = useState("");
+ const [folders, setFolders] = useState([]);
+ const [showSaveModal, setShowSaveModal] = useState(false);
+ const [showCreateFolderModal, setShowCreateFolderModal] = useState(false);
+ const [showLoadModal, setShowLoadModal] = useState(false);
+ const [newFolderTitle, setNewFolderTitle] = useState("");
+ const [selectedFolder, setSelectedFolder] = useState("");
+ const [saved, setSaved] = useState(false);
+ const [error, setError] = useState("");
+ const [showDropdown, setShowDropdown] = useState(false);
+ const [editingPageId, setEditingPageId] = useState(null);
+ const [loadFolderId, setLoadFolderId] = useState("");
+ const [loadPages, setLoadPages] = useState([]);
+ const [loadingPages, setLoadingPages] = useState(false);
+ const [inlineFolderTitle, setInlineFolderTitle] = useState("");
+ const [showNewPageModal, setShowNewPageModal] = useState(false);
+ const [clearAfterSave, setClearAfterSave] = useState(false);
+
+ const dropdownRef = useRef(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const load = async () => {
+ const data = await getFolders();
+ if (data) setFolders(data);
+ };
+ load();
+ }, []);
+
+ useEffect(() => {
+ const stored = localStorage.getItem("pz_edit_page");
+ if (stored) {
+ const page = JSON.parse(stored);
+ setTitle(page.title);
+ setContent(page.content);
+ setEditingPageId(page.id);
+ localStorage.removeItem("pz_edit_page");
+ }
+ }, []);
+
+ useEffect(() => {
+ const handleClickOutside = (e) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
+ setShowDropdown(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ useEffect(() => {
+ if (!loadFolderId) { setLoadPages([]); return; }
+ const load = async () => {
+ setLoadingPages(true);
+ const data = await getPages(parseInt(loadFolderId));
+ if (data) setLoadPages(data);
+ setLoadingPages(false);
+ };
+ load();
+ }, [loadFolderId]);
+
+ const handleSave = async () => {
+ if (!selectedFolder) return;
+ if (!title.trim()) {
+ setError("Title cannot be empty.");
+ return;
+ }
+ const data = await createPage(parseInt(selectedFolder), title.trim(), content.trim());
+ if (data) {
+ setSaved(true);
+ setShowSaveModal(false);
+ if (clearAfterSave) {
+ clearEditor();
+ setClearAfterSave(false);
+ } else {
+ setTitle("");
+ setContent("");
+ setSelectedFolder("");
+ setEditingPageId(null);
+ setError("");
+ }
+ setTimeout(() => setSaved(false), 3000);
+ }
+ };
+
+ const handleOpenSaveModal = () => {
+ if (!title.trim()) {
+ setError("Write a title before saving.");
+ return;
+ }
+ setError("");
+ setInlineFolderTitle("");
+ setShowSaveModal(true);
+ };
+
+
+ const handleInlineCreateFolder = async () => {
+ if (!inlineFolderTitle.trim()) return;
+ const data = await createFolder(inlineFolderTitle.trim());
+ if (data) {
+ const updatedFolders = [...folders, data];
+ setFolders(updatedFolders);
+ setSelectedFolder(String(data.id));
+ setInlineFolderTitle("");
+ }
+ };
+
+ const handleCreateFolder = async () => {
+ if (!newFolderTitle.trim()) return;
+ const data = await createFolder(newFolderTitle.trim());
+ if (data) {
+ setFolders(prev => [...prev, data]);
+ setNewFolderTitle("");
+ setShowCreateFolderModal(false);
+ }
+ };
+
+ const handleNewPage = () => {
+ setShowDropdown(false);
+ if (title.trim() || content.trim()) {
+ setShowNewPageModal(true);
+ } else {
+ clearEditor();
+ }
+ };
+
+ const clearEditor = () => {
+ setTitle("");
+ setContent("");
+ setEditingPageId(null);
+ setError("");
+ };
+
+ const handleSaveAndNew = () => {
+ setShowNewPageModal(false);
+ setInlineFolderTitle("");
+ setSelectedFolder("");
+ setClearAfterSave(true);
+ setShowSaveModal(true);
+ };
+
+ const handleDiscardAndNew = () => {
+ setShowNewPageModal(false);
+ clearEditor();
+ };
+
+ const handleSelectLoadPage = (page) => {
+ setTitle(page.title);
+ setContent(page.content);
+ setEditingPageId(page.id);
+ setShowLoadModal(false);
+ setLoadFolderId("");
+ setLoadPages([]);
+ };
+
+ const handleCancelLoad = () => {
+ setShowLoadModal(false);
+ setLoadFolderId("");
+ setLoadPages([]);
+ };
+
+ return (
+
+
+
+ {editingPageId ? "Editing page" : "start writing..."}
+
+
+ {saved &&
✓ saved }
+
+
+ save
+
+
+
+
setShowDropdown(!showDropdown)}
+ >+
+ {showDropdown && (
+
+ New page
+ { setShowDropdown(false); setShowCreateFolderModal(true); }}
+ >New folder
+ { setShowDropdown(false); setShowLoadModal(true); }}
+ >Load page
+ { setShowDropdown(false); navigate("/folders"); }}
+ >Go to files
+
+ )}
+
+
+
+
+
setTitle(e.target.value)}
+ />
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/PomodoroZone.jsx b/src/front/components/PomodoroZone.jsx
new file mode 100644
index 0000000000..897d6098d4
--- /dev/null
+++ b/src/front/components/PomodoroZone.jsx
@@ -0,0 +1,259 @@
+import "../styles/pomodoroZone.css";
+import { useMemo, useRef, useEffect, useState } from "react";
+import { BreakModal } from "./BreakModal";
+import useGlobalReducer from "../hooks/useGlobalReducer";
+import { TIMER_PRESETS_LIST } from "../store";
+import { useNavigate } from "react-router-dom";
+
+const formatTotal = (s) => {
+ const h = Math.floor(s / 3600);
+ const m = Math.floor((s % 3600) / 60);
+ const sec = s % 60;
+ if (h > 0) {
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
+ }
+ return `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
+};
+
+const hasHours = (s) => s >= 3600;
+
+export const PomodoroZone = () => {
+ const { store, dispatch, audioRef } = useGlobalReducer();
+ const p = store.pomodoro;
+ const { currentPlaylist, currentTrackIndex, isPlaying } = store;
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const [audioProgress, setAudioProgress] = useState(0);
+ const [isDragging, setIsDragging] = useState(false);
+ const progressRef = useRef(null);
+ const dropdownRef = useRef(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!isMenuOpen) return;
+ const handleClickOutside = (e) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
+ setIsMenuOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, [isMenuOpen]);
+
+ useEffect(() => {
+ const audio = audioRef?.current;
+ if (!audio) return;
+ const update = () => {
+ if (audio.duration) {
+ setAudioProgress(audio.currentTime / audio.duration);
+ }
+ };
+ audio.addEventListener("timeupdate", update);
+ return () => audio.removeEventListener("timeupdate", update);
+ }, [audioRef, currentTrackIndex]);
+
+ const handleSeek = (e) => {
+ const audio = audioRef?.current;
+ if (!audio || !audio.duration) return;
+ const rect = progressRef.current.getBoundingClientRect();
+ const ratio = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1);
+ audio.currentTime = ratio * audio.duration;
+ setAudioProgress(ratio);
+ };
+
+ const handleDragStart = (e) => {
+ e.preventDefault();
+ if (!audioRef?.current?.duration) return;
+ setIsDragging(true);
+ const onMove = (ev) => {
+ const rect = progressRef.current.getBoundingClientRect();
+ const ratio = Math.min(Math.max((ev.clientX - rect.left) / rect.width, 0), 1);
+ setAudioProgress(ratio);
+ };
+ const onUp = (ev) => {
+ const rect = progressRef.current.getBoundingClientRect();
+ const ratio = Math.min(Math.max((ev.clientX - rect.left) / rect.width, 0), 1);
+ audioRef.current.currentTime = ratio * audioRef.current.duration;
+ setAudioProgress(ratio);
+ setIsDragging(false);
+ document.removeEventListener("mousemove", onMove);
+ document.removeEventListener("mouseup", onUp);
+ };
+ document.addEventListener("mousemove", onMove);
+ document.addEventListener("mouseup", onUp);
+ };
+
+ const phaseLabel = useMemo(() => {
+ return p.currentPhase === "focus" ? "time to focus!" : "break time";
+ }, [p.currentPhase]);
+
+ const startBtnLabel = p.breakSkipped ? "continue break" : p.isRunning ? "pause" : "start";
+ const startBtnClass = `pomodoro-start-button${p.isRunning ? " is-running" : ""}${p.breakSkipped ? " is-break-skipped" : ""}`;
+
+ const handleStartPause = () => dispatch({ type: "pomodoro_start_pause" });
+ const handleContinueBreak = () => dispatch({ type: "pomodoro_continue_break" });
+ const handleSkipBreak = () => dispatch({ type: "pomodoro_skip_break" });
+ const handleBreakEnd = () => dispatch({ type: "pomodoro_break_end" });
+ const handleRestorePomodoro = () => dispatch({ type: "pomodoro_restore" });
+ const handlePresetSelect = (preset) => {
+ dispatch({ type: "pomodoro_set_preset", payload: preset });
+ setIsMenuOpen(false);
+ };
+
+ const handlePreviousTrack = () => {
+ if (!currentPlaylist || currentTrackIndex <= 0) return;
+ dispatch({ type: "set_track_index", payload: currentTrackIndex - 1 });
+ };
+
+ const handleTogglePlay = () => {
+ dispatch({ type: "set_playing", payload: !isPlaying });
+ };
+
+ const handleNextTrack = () => {
+ if (!currentPlaylist || currentTrackIndex >= currentPlaylist.sounds.length - 1) return;
+ dispatch({ type: "set_track_index", payload: currentTrackIndex + 1 });
+ };
+
+ return (
+ <>
+
+
+
+
setIsMenuOpen(!isMenuOpen)}
+ >
+ Pomodoro Time
+
+
+ {isMenuOpen && (
+
+ {TIMER_PRESETS_LIST.map((preset) => (
+ handlePresetSelect(preset)}
+ >
+ {preset.label}
+
+ ))}
+
+ )}
+
+
+
+
+
+ {formatTotal(p.focusLeft)}
+
+
{phaseLabel}
+
+
+ {startBtnLabel}
+
+
+ {p.breakSkipped && (
+
+ restore pomodoro
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {isPlaying
+ ?
+ :
+ }
+
+
= (currentPlaylist?.sounds.length - 1)}
+ style={{
+ width: "36px", height: "36px", borderRadius: "50%",
+ border: "1.5px solid #2D3A4A", background: "transparent",
+ cursor: "pointer", display: "flex", alignItems: "center",
+ justifyContent: "center", opacity: (!currentPlaylist || currentTrackIndex >= (currentPlaylist?.sounds.length - 1)) ? 0.3 : 1
+ }}
+ >
+
+
+
+
+
navigate("/music")}
+ >
+ choose music
+
+
+
+
+ {p.showBreakModal && (
+
+ )}
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/RegisterModal.jsx b/src/front/components/RegisterModal.jsx
new file mode 100644
index 0000000000..8c04922609
--- /dev/null
+++ b/src/front/components/RegisterModal.jsx
@@ -0,0 +1,198 @@
+import { registerUser } from "../services/registerBS";
+import "../styles/registerModal.css";
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+
+const PASSWORD_RULES = [
+ { id: "length", label: "At least 8 characters", test: (p) => p.length >= 8 },
+ { id: "number", label: "At least 1 number", test: (p) => /\d/.test(p) },
+ { id: "symbol", label: "At least 1 symbol (!@#$…)", test: (p) => /[^a-zA-Z0-9]/.test(p) },
+];
+
+const getStrength = (password) => {
+ const passed = PASSWORD_RULES.filter(r => r.test(password)).length;
+ if (passed === 0) return { level: 0, label: "", color: "transparent" };
+ if (passed === 1) return { level: 1, label: "Weak", color: "#E05252" };
+ if (passed === 2) return { level: 2, label: "Fair", color: "#E0A852" };
+ return { level: 3, label: "Strong", color: "#52A87C" };
+};
+
+export const RegisterModal = ({ onClose, onSwitchToLogin }) => {
+
+ const [error, setError] = useState("");
+ const [user, setUser] = useState({
+ name: "",
+ email: "",
+ password: "",
+ confirmPassword: "",
+ });
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [passwordTouched, setPasswordTouched] = useState(false);
+ const [success, setSuccess] = useState(false);
+ const navigate = useNavigate();
+
+ const strength = getStrength(user.password);
+ const allRulesPassed = PASSWORD_RULES.every(r => r.test(user.password));
+
+ const handleOverlayClick = (e) => {
+ if (e.target === e.currentTarget) onClose();
+ };
+
+ const handleChange = (e) => {
+ setUser({ ...user, [e.target.name]: e.target.value });
+ };
+
+ const handlePasswordChange = (e) => {
+ setPasswordTouched(true);
+ handleChange(e);
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!user.name || !user.email || !user.password || !user.confirmPassword) {
+ setError("All fields are required.");
+ return;
+ }
+ if (!allRulesPassed) {
+ setError("Your password doesn't meet the security requirements.");
+ return;
+ }
+ if (user.password !== user.confirmPassword) {
+ setError("Passwords do not match.");
+ return;
+ }
+ try {
+ await registerUser({ ...user, email: user.email.trim().toLowerCase() });
+ setSuccess(true);
+ setTimeout(() => {
+ onSwitchToLogin();
+ }, 2000);
+ } catch (err) {
+ setError(err.message);
+ }
+ };
+
+ return (
+
+
+
+
✕
+
+
register
+
+
+
+
+ Already have an account?{" "}
+ Log in
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/ScrollStack.jsx b/src/front/components/ScrollStack.jsx
new file mode 100644
index 0000000000..ff2151e238
--- /dev/null
+++ b/src/front/components/ScrollStack.jsx
@@ -0,0 +1,185 @@
+import { useLayoutEffect, useRef, useCallback } from 'react';
+import Lenis from 'lenis';
+import '../styles/ScrollStack.css';
+
+export const ScrollStackItem = ({ children, itemClassName = '' }) => (
+ {children}
+);
+
+const ScrollStack = ({
+ children,
+ className = '',
+ itemDistance = 100,
+ itemScale = 0.03,
+ itemStackDistance = 30,
+ stackPosition = '20%',
+ scaleEndPosition = '10%',
+ baseScale = 0.85,
+ scaleDuration = 0.5,
+ rotationAmount = 0,
+ blurAmount = 0,
+ useWindowScroll = false,
+ onStackComplete
+}) => {
+ const scrollerRef = useRef(null);
+ const stackCompletedRef = useRef(false);
+ const animationFrameRef = useRef(null);
+ const lenisRef = useRef(null);
+ const cardsRef = useRef([]);
+ const lastTransformsRef = useRef(new Map());
+ const isUpdatingRef = useRef(false);
+
+ const calculateProgress = useCallback((scrollTop, start, end) => {
+ if (scrollTop < start) return 0;
+ if (scrollTop > end) return 1;
+ return (scrollTop - start) / (end - start);
+ }, []);
+
+ const parsePercentage = useCallback((value, containerHeight) => {
+ if (typeof value === 'string' && value.includes('%')) {
+ return (parseFloat(value) / 100) * containerHeight;
+ }
+ return parseFloat(value);
+ }, []);
+
+ const getScrollData = useCallback(() => {
+ if (useWindowScroll) {
+ return { scrollTop: window.scrollY, containerHeight: window.innerHeight, scrollContainer: document.documentElement };
+ } else {
+ const scroller = scrollerRef.current;
+ return { scrollTop: scroller.scrollTop, containerHeight: scroller.clientHeight, scrollContainer: scroller };
+ }
+ }, [useWindowScroll]);
+
+ const getElementOffset = useCallback(element => {
+ if (useWindowScroll) {
+ const rect = element.getBoundingClientRect();
+ return rect.top + window.scrollY;
+ } else {
+ return element.offsetTop;
+ }
+ }, [useWindowScroll]);
+
+ const updateCardTransforms = useCallback(() => {
+ if (!cardsRef.current.length || isUpdatingRef.current) return;
+ isUpdatingRef.current = true;
+ const { scrollTop, containerHeight } = getScrollData();
+ const stackPositionPx = parsePercentage(stackPosition, containerHeight);
+ const scaleEndPositionPx = parsePercentage(scaleEndPosition, containerHeight);
+ const endElement = useWindowScroll
+ ? document.querySelector('.scroll-stack-end')
+ : scrollerRef.current?.querySelector('.scroll-stack-end');
+ const endElementTop = endElement ? getElementOffset(endElement) : 0;
+
+ cardsRef.current.forEach((card, i) => {
+ if (!card) return;
+ const cardTop = getElementOffset(card);
+ const triggerStart = cardTop - stackPositionPx - itemStackDistance * i;
+ const triggerEnd = cardTop - scaleEndPositionPx;
+ const pinStart = cardTop - stackPositionPx - itemStackDistance * i;
+ const pinEnd = endElementTop - containerHeight / 2;
+ const scaleProgress = calculateProgress(scrollTop, triggerStart, triggerEnd);
+ const targetScale = baseScale + i * itemScale;
+ const scale = 1 - scaleProgress * (1 - targetScale);
+ const rotation = rotationAmount ? i * rotationAmount * scaleProgress : 0;
+ let blur = 0;
+ if (blurAmount) {
+ let topCardIndex = 0;
+ for (let j = 0; j < cardsRef.current.length; j++) {
+ const jCardTop = getElementOffset(cardsRef.current[j]);
+ const jTriggerStart = jCardTop - stackPositionPx - itemStackDistance * j;
+ if (scrollTop >= jTriggerStart) topCardIndex = j;
+ }
+ if (i < topCardIndex) blur = Math.max(0, (topCardIndex - i) * blurAmount);
+ }
+ let translateY = 0;
+ const isPinned = scrollTop >= pinStart && scrollTop <= pinEnd;
+ if (isPinned) {
+ translateY = scrollTop - cardTop + stackPositionPx + itemStackDistance * i;
+ } else if (scrollTop > pinEnd) {
+ translateY = pinEnd - cardTop + stackPositionPx + itemStackDistance * i;
+ }
+ const newTransform = {
+ translateY: Math.round(translateY * 100) / 100,
+ scale: Math.round(scale * 1000) / 1000,
+ rotation: Math.round(rotation * 100) / 100,
+ blur: Math.round(blur * 100) / 100
+ };
+ const lastTransform = lastTransformsRef.current.get(i);
+ const hasChanged = !lastTransform ||
+ Math.abs(lastTransform.translateY - newTransform.translateY) > 0.1 ||
+ Math.abs(lastTransform.scale - newTransform.scale) > 0.001 ||
+ Math.abs(lastTransform.rotation - newTransform.rotation) > 0.1 ||
+ Math.abs(lastTransform.blur - newTransform.blur) > 0.1;
+ if (hasChanged) {
+ card.style.transform = `translate3d(0, ${newTransform.translateY}px, 0) scale(${newTransform.scale}) rotate(${newTransform.rotation}deg)`;
+ card.style.filter = newTransform.blur > 0 ? `blur(${newTransform.blur}px)` : '';
+ lastTransformsRef.current.set(i, newTransform);
+ }
+ if (i === cardsRef.current.length - 1) {
+ const isInView = scrollTop >= pinStart && scrollTop <= pinEnd;
+ if (isInView && !stackCompletedRef.current) { stackCompletedRef.current = true; onStackComplete?.(); }
+ else if (!isInView && stackCompletedRef.current) stackCompletedRef.current = false;
+ }
+ });
+ isUpdatingRef.current = false;
+ }, [itemScale, itemStackDistance, stackPosition, scaleEndPosition, baseScale, rotationAmount, blurAmount, useWindowScroll, onStackComplete, calculateProgress, parsePercentage, getScrollData, getElementOffset]);
+
+ const handleScroll = useCallback(() => { updateCardTransforms(); }, [updateCardTransforms]);
+
+ const setupLenis = useCallback(() => {
+ if (useWindowScroll) {
+ const lenis = new Lenis({ duration: 1.2, easing: t => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true });
+ lenis.on('scroll', handleScroll);
+ const raf = time => { lenis.raf(time); animationFrameRef.current = requestAnimationFrame(raf); };
+ animationFrameRef.current = requestAnimationFrame(raf);
+ lenisRef.current = lenis;
+ return lenis;
+ } else {
+ const scroller = scrollerRef.current;
+ if (!scroller) return;
+ const lenis = new Lenis({ wrapper: scroller, content: scroller.querySelector('.scroll-stack-inner'), duration: 1.2, easing: t => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true, lerp: 0.1 });
+ lenis.on('scroll', handleScroll);
+ const raf = time => { lenis.raf(time); animationFrameRef.current = requestAnimationFrame(raf); };
+ animationFrameRef.current = requestAnimationFrame(raf);
+ lenisRef.current = lenis;
+ return lenis;
+ }
+ }, [handleScroll, useWindowScroll]);
+
+ useLayoutEffect(() => {
+ const scroller = scrollerRef.current;
+ if (!scroller) return;
+ const cards = Array.from(useWindowScroll ? document.querySelectorAll('.scroll-stack-card') : scroller.querySelectorAll('.scroll-stack-card'));
+ cardsRef.current = cards;
+ const transformsCache = lastTransformsRef.current;
+ cards.forEach((card, i) => {
+ if (i < cards.length - 1) card.style.marginBottom = `${itemDistance}px`;
+ card.style.willChange = 'transform, filter';
+ card.style.transformOrigin = 'top center';
+ card.style.backfaceVisibility = 'hidden';
+ card.style.transform = 'translateZ(0)';
+ });
+ setupLenis();
+ updateCardTransforms();
+ return () => {
+ if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
+ if (lenisRef.current) lenisRef.current.destroy();
+ stackCompletedRef.current = false;
+ cardsRef.current = [];
+ transformsCache.clear();
+ isUpdatingRef.current = false;
+ };
+ }, [itemDistance, itemScale, itemStackDistance, stackPosition, scaleEndPosition, baseScale, scaleDuration, rotationAmount, blurAmount, useWindowScroll, onStackComplete, setupLenis, updateCardTransforms]);
+
+ return (
+
+ );
+};
+
+export default ScrollStack;
\ No newline at end of file
diff --git a/src/front/components/TiltedCard.jsx b/src/front/components/TiltedCard.jsx
new file mode 100644
index 0000000000..27bb0fd425
--- /dev/null
+++ b/src/front/components/TiltedCard.jsx
@@ -0,0 +1,69 @@
+import { useRef, useState } from 'react';
+import { motion, useMotionValue, useSpring } from 'motion/react';
+import '../styles/TiltedCard.css';
+
+const springValues = { damping: 30, stiffness: 100, mass: 2 };
+
+export default function TiltedCard({
+ imageSrc,
+ altText = 'Tilted card image',
+ captionText = '',
+ containerHeight = '300px',
+ containerWidth = '100%',
+ imageHeight = '300px',
+ imageWidth = '300px',
+ scaleOnHover = 1.1,
+ rotateAmplitude = 14,
+ showMobileWarning = false,
+ showTooltip = true,
+ overlayContent = null,
+ displayOverlayContent = false
+}) {
+ const ref = useRef(null);
+ const x = useMotionValue();
+ const y = useMotionValue();
+ const rotateX = useSpring(useMotionValue(0), springValues);
+ const rotateY = useSpring(useMotionValue(0), springValues);
+ const scale = useSpring(1, springValues);
+ const opacity = useSpring(0);
+ const rotateFigcaption = useSpring(0, { stiffness: 350, damping: 30, mass: 1 });
+ const [lastY, setLastY] = useState(0);
+
+ function handleMouse(e) {
+ if (!ref.current) return;
+ const rect = ref.current.getBoundingClientRect();
+ const offsetX = e.clientX - rect.left - rect.width / 2;
+ const offsetY = e.clientY - rect.top - rect.height / 2;
+ rotateX.set((offsetY / (rect.height / 2)) * -rotateAmplitude);
+ rotateY.set((offsetX / (rect.width / 2)) * rotateAmplitude);
+ x.set(e.clientX - rect.left);
+ y.set(e.clientY - rect.top);
+ rotateFigcaption.set(-(offsetY - lastY) * 0.6);
+ setLastY(offsetY);
+ }
+
+ function handleMouseEnter() { scale.set(scaleOnHover); opacity.set(1); }
+ function handleMouseLeave() { opacity.set(0); scale.set(1); rotateX.set(0); rotateY.set(0); rotateFigcaption.set(0); }
+
+ return (
+
+ {showMobileWarning && This effect is not optimized for mobile.
}
+
+
+ {displayOverlayContent && overlayContent && {overlayContent} }
+
+ {showTooltip && (
+
+ {captionText}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/front/components/TypingText.jsx b/src/front/components/TypingText.jsx
new file mode 100644
index 0000000000..5aacee43c5
--- /dev/null
+++ b/src/front/components/TypingText.jsx
@@ -0,0 +1,39 @@
+import { useEffect, useState } from "react";
+
+const phrases = ["Your focus", "Your rhythm", "Your progress"];
+
+export const TypingText = () => {
+ const [displayed, setDisplayed] = useState("");
+ const [phraseIndex, setPhraseIndex] = useState(0);
+ const [charIndex, setCharIndex] = useState(0);
+ const [deleting, setDeleting] = useState(false);
+
+ useEffect(() => {
+ const current = phrases[phraseIndex];
+ const speed = deleting ? 50 : 30;
+
+ const timeout = setTimeout(() => {
+ if (!deleting && charIndex < current.length) {
+ setDisplayed(current.slice(0, charIndex + 1));
+ setCharIndex(charIndex + 1);
+ } else if (!deleting && charIndex === current.length) {
+ setTimeout(() => setDeleting(true), 1500);
+ } else if (deleting && charIndex > 0) {
+ setDisplayed(current.slice(0, charIndex - 1));
+ setCharIndex(charIndex - 1);
+ } else if (deleting && charIndex === 0) {
+ setDeleting(false);
+ setPhraseIndex((phraseIndex + 1) % phrases.length);
+ }
+ }, speed);
+
+ return () => clearTimeout(timeout);
+ }, [charIndex, deleting, phraseIndex]);
+
+ return (
+
+ {displayed}
+ |
+
+ );
+};
diff --git a/src/front/components/goals/Goals.css b/src/front/components/goals/Goals.css
new file mode 100644
index 0000000000..ba9a8bf966
--- /dev/null
+++ b/src/front/components/goals/Goals.css
@@ -0,0 +1,281 @@
+
+.goals-page {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+
+.goals-title {
+ text-align: center;
+ margin-bottom: 30px;
+ color: var(--color-text-primary);
+}
+
+
+.goals-layout {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 24px;
+ align-items: start;
+}
+
+.goals-column {
+ display: flex;
+ flex-direction: column;
+}
+
+
+.goal-create {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 24px;
+}
+
+.goal-input {
+ flex: 1;
+ padding: 12px;
+ border-radius: 10px;
+ border: 1px solid var(--color-divider);
+ background: var(--color-surface);
+ color: var(--color-text-primary);
+ outline: none;
+}
+
+.goal-input:focus {
+ border-color: var(--color-btn-primary-bg);
+}
+
+
+.goal-card {
+ background: var(--color-surface);
+ padding: 20px;
+ border-radius: 12px;
+ margin-bottom: 16px;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+ transition: all 0.2s ease;
+}
+
+.goal-card:hover {
+ transform: translateY(-3px);
+}
+
+
+.goal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.goal-left {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.goal-card h3 {
+ margin: 0;
+ font-size: 1.1rem;
+ cursor: pointer;
+ color: var(--color-text-primary);
+}
+
+
+.edit-input {
+ border: 1px solid var(--color-divider);
+ border-radius: 6px;
+ padding: 6px 8px;
+ font-size: 1rem;
+ width: 100%;
+ background: var(--color-surface);
+ color: var(--color-text-primary);
+}
+
+
+.checkbox {
+ width: 18px;
+ height: 18px;
+ border: 2px solid var(--color-divider);
+ border-radius: 5px;
+ cursor: pointer;
+ transition: 0.2s;
+}
+
+.checkbox.checked {
+ background: #e63946;
+ border-color: #e63946;
+}
+
+
+.goal-status-wrapper {
+ max-height: 0;
+ overflow: hidden;
+ opacity: 0;
+ transition: all 0.3s ease;
+}
+
+.goal-status-wrapper.show {
+ max-height: 60px;
+ opacity: 1;
+}
+
+
+.goal-status {
+ display: flex;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.status-btn {
+ border: none;
+ padding: 6px 0;
+ font-size: 0.90rem;
+ border-radius: 25px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ flex: 1;
+ text-align: center;
+ white-space: nowrap;
+ line-height: 1.5;
+
+
+ height: auto;
+ overflow: visible;
+ display: inline-block;
+}
+
+
+.status-btn.urgent {
+ background: rgba(230, 57, 70, 0.15);
+ color: #e63946;
+}
+
+.status-btn.urgent.active {
+ background: #e63946;
+ color: white;
+}
+
+
+.status-btn.progress {
+ background: rgba(168, 218, 220, 0.3);
+ color: #457b9d;
+}
+
+.status-btn.progress.active {
+ background: #457b9d;
+ color: white;
+}
+
+
+.status-btn.done {
+ background: rgba(69, 123, 157, 0.15);
+ color: #457b9d;
+}
+
+.status-btn.done.active {
+ background: #457b9d;
+ color: white;
+}
+
+
+.goal-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.goal-actions button {
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-size: 14px;
+ opacity: 0.6;
+ transition: all 0.2s ease;
+}
+
+.goal-actions button:hover {
+ opacity: 1;
+ transform: scale(1.1);
+}
+
+
+.chart-column {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+.chart-card {
+ background: var(--color-surface);
+ padding: 2rem;
+ border-radius: 16px;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+ text-align: center;
+}
+
+
+.pie {
+ width: 190px;
+ height: 190px;
+ border-radius: 50%;
+ margin: auto;
+ position: relative;
+}
+
+.pie::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 110px;
+ height: 110px;
+ background: var(--color-surface);
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.legend {
+ margin-top: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 0.9rem;
+ color: var(--color-text-secondary);
+}
+
+@media (max-width: 768px) {
+ .goals-page {
+ padding: 20px 16px;
+ }
+
+ .goals-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .chart-column {
+ order: -1;
+ }
+
+ .chart-card {
+ padding: 1.2rem;
+ }
+
+ .pie {
+ width: 140px;
+ height: 140px;
+ }
+
+ .pie::after {
+ width: 80px;
+ height: 80px;
+ }
+
+ .goal-create {
+ flex-direction: column;
+ }
+
+ .status-btn {
+ font-size: 0.78rem;
+ padding: 5px 0;
+ }
+}
\ No newline at end of file
diff --git a/src/front/components/goals/Goals.jsx b/src/front/components/goals/Goals.jsx
new file mode 100644
index 0000000000..306f7d7229
--- /dev/null
+++ b/src/front/components/goals/Goals.jsx
@@ -0,0 +1,207 @@
+import { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import "./Goals.css";
+
+import {
+ getGoals,
+ createGoal,
+ updateGoal,
+ deleteGoal
+} from "./GoalsService";
+
+export const Goals = () => {
+ const [goals, setGoals] = useState([]);
+ const [newGoal, setNewGoal] = useState("");
+ const [editingId, setEditingId] = useState(null);
+ const [editingText, setEditingText] = useState("");
+ const [selectedGoal, setSelectedGoal] = useState(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ loadGoals();
+
+ const handleRefresh = () => loadGoals();
+ window.addEventListener("goals:updated", handleRefresh);
+ document.addEventListener("visibilitychange", () => {
+ if (!document.hidden) loadGoals();
+ });
+ return () => {
+ window.removeEventListener("goals:updated", handleRefresh);
+ };
+ }, []);
+
+ const loadGoals = async () => {
+ const data = await getGoals();
+ if (data) setGoals(data.filter(g => g));
+ };
+
+ const handleCreateGoal = async () => {
+ if (!newGoal.trim()) return;
+ const data = await createGoal(newGoal, newGoal);
+ if (!data?.goal) return;
+ setNewGoal("");
+ await loadGoals();
+ };
+
+ const startEditing = (goal) => {
+ setEditingId(goal.id);
+ setEditingText(goal.title);
+ };
+
+ const saveEdit = async (id) => {
+ if (!editingText.trim()) return;
+ const data = await updateGoal(id, { title: editingText });
+ if (!data?.goal) return;
+ setGoals(goals.map(g => (g.id === id ? data.goal : g)));
+ setEditingId(null);
+ setEditingText("");
+ };
+
+ const handleDelete = async (id) => {
+ await deleteGoal(id);
+ setGoals(goals.filter(g => g.id !== id));
+ if (selectedGoal === id) setSelectedGoal(null);
+ };
+
+ const changeStatus = async (id, status) => {
+ const data = await updateGoal(id, { status });
+ if (!data?.goal) return;
+ setGoals(goals.map(g => (g.id === id ? data.goal : g)));
+ };
+
+ const stats = {
+ urgent: goals.filter(g => g.status === "urgent").length,
+ progress: goals.filter(g => g.status === "progress").length,
+ done: goals.filter(g => g.status === "done").length
+ };
+
+ const total = goals.length || 1;
+
+ return (
+
+
+
navigate("/home")}
+ style={{
+ margin: "0 0 1rem 0",
+ padding: "8px 16px",
+ borderRadius: "8px",
+ border: "1.5px solid var(--color-divider)",
+ background: "transparent",
+ color: "var(--color-text-primary)",
+ cursor: "pointer",
+ fontSize: "0.9rem"
+ }}
+ >
+ ← Back to home
+
+
+
Your Goals
+
+
+
+ {/* LEFT COLUMN */}
+
+
+
+ setNewGoal(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleCreateGoal()}
+ />
+
+ Add
+
+
+
+ {goals.map(goal => (
+
+
+
+
+ {editingId === goal.id ? (
+ setEditingText(e.target.value)}
+ onBlur={() => saveEdit(goal.id)}
+ onKeyDown={(e) => e.key === "Enter" && saveEdit(goal.id)}
+ autoFocus
+ />
+ ) : (
+
startEditing(goal)}>
+ {goal.title}
+
+ )}
+
+
+
+ setSelectedGoal(selectedGoal === goal.id ? null : goal.id)
+ }
+ />
+
+
+
+
+ changeStatus(goal.id, "urgent")}
+ >
+ Urgent
+
+ changeStatus(goal.id, "progress")}
+ >
+ In Progress
+
+ changeStatus(goal.id, "done")}
+ >
+ Done
+
+
+
+
+
+ startEditing(goal)}>✏
+ handleDelete(goal.id)}>🗑
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Urgent: {stats.urgent}
+ Progress: {stats.progress}
+ Done: {stats.done}
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/components/goals/GoalsService.js b/src/front/components/goals/GoalsService.js
new file mode 100644
index 0000000000..ca40a39db6
--- /dev/null
+++ b/src/front/components/goals/GoalsService.js
@@ -0,0 +1,57 @@
+const backend_url = import.meta.env.VITE_BACKEND_URL; // ej: http://localhost:3001
+
+
+export const getGoals = async () => {
+ const response = await fetch(`${backend_url}/api/goals/`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+
+export const getGoal = async (goalId) => {
+ const response = await fetch(`${backend_url}/api/goals/${goalId}`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+
+export const createGoal = async (title, content = "") => {
+ const response = await fetch(`${backend_url}/api/goals/`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify({ title, content })
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+
+export const updateGoal = async (goalId, updatedData) => {
+ const response = await fetch(`${backend_url}/api/goals/${goalId}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify(updatedData)
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+
+export const deleteGoal = async (goalId) => {
+ const response = await fetch(`${backend_url}/api/goals/${goalId}`, {
+ method: "DELETE",
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/FolderPanel.css b/src/front/components/pages-y-folder/FolderPanel.css
new file mode 100644
index 0000000000..84e2c99c71
--- /dev/null
+++ b/src/front/components/pages-y-folder/FolderPanel.css
@@ -0,0 +1,118 @@
+.folder-list-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.folder-scroll-list {
+ height: 100%;
+ overflow-y: auto;
+ padding: 12px;
+}
+
+.folder-scroll-list::-webkit-scrollbar { width: 4px; }
+.folder-scroll-list::-webkit-scrollbar-track { background: transparent; }
+.folder-scroll-list::-webkit-scrollbar-thumb { background: #B0BBA8; border-radius: 4px; }
+
+.folder-animated-item {
+ padding: 10px 12px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: border-color 0.2s ease, background 0.2s ease;
+ margin-bottom: 6px;
+}
+
+.folder-animated-item:hover { background-color: rgba(45,58,74,0.04); }
+.folder-animated-item.selected { background-color: rgba(45,58,74,0.08); border-color: #B0BBA8; }
+
+.folder-animated-item .item-text {
+ color: #1A1A1A;
+ margin: 0;
+ font-size: 14px;
+ font-weight: 400;
+ flex: 1;
+}
+
+.folder-animated-item.selected .item-text { font-weight: 500; color: #2D3A4A; }
+
+.folder-top-gradient {
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 50px;
+ background: linear-gradient(to bottom, #F2F5F0, transparent);
+ pointer-events: none;
+ transition: opacity 0.3s ease;
+}
+
+.folder-bottom-gradient {
+ position: absolute;
+ bottom: 0; left: 0; right: 0;
+ height: 80px;
+ background: linear-gradient(to top, #F2F5F0, transparent);
+ pointer-events: none;
+ transition: opacity 0.3s ease;
+}
+
+.folder-edit-btn {
+ background: rgba(45,58,74,0.08);
+ border: none;
+ border-radius: 6px;
+ width: 26px;
+ height: 26px;
+ cursor: pointer;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.2s ease;
+}
+
+.folder-edit-btn:hover { background: rgba(45,58,74,0.15); }
+
+.folder-rename-input {
+ flex: 1;
+ padding: 4px 8px;
+ border: 1px solid #2D3A4A;
+ border-radius: 6px;
+ font-size: 13px;
+ color: #1A1A1A;
+ outline: none;
+ background: white;
+}
+
+.folder-rename-btn {
+ width: 26px;
+ height: 26px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.2s ease;
+}
+
+.folder-rename-btn.confirm { background: rgba(45,58,74,0.08); color: #2D3A4A; }
+.folder-rename-btn.confirm:hover { background: rgba(45,58,74,0.15); }
+.folder-rename-btn.cancel { background: rgba(220,38,38,0.08); color: #dc2626; }
+.folder-rename-btn.cancel:hover { background: rgba(220,38,38,0.15); }
+
+/* RESPONSIVE */
+@media (max-width: 768px) {
+ .folder-scroll-list {
+ padding: 8px;
+ max-height: 250px;
+ }
+
+ .folder-animated-item {
+ padding: 8px 10px;
+ }
+}
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/FolderPanel.jsx b/src/front/components/pages-y-folder/FolderPanel.jsx
new file mode 100644
index 0000000000..0c5a838c4b
--- /dev/null
+++ b/src/front/components/pages-y-folder/FolderPanel.jsx
@@ -0,0 +1,161 @@
+import { useRef, useState, useCallback } from 'react';
+import { motion, useInView } from 'motion/react';
+import './FolderPanel.css';
+
+const FolderClosedIcon = () => (
+
+
+
+);
+
+const FolderOpenIcon = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {
+ const ref = useRef(null);
+ const inView = useInView(ref, { amount: 0.5, triggerOnce: false });
+ return (
+
+ {children}
+
+ );
+};
+
+const FolderPanel = ({
+ folders = [],
+ pages = [],
+ activeFolder = null,
+ onSelectFolder,
+ editMode = false,
+ selectedFolders = [],
+ onToggleSelect,
+ onRenameFolder,
+ showGradients = true,
+ displayScrollbar = true,
+}) => {
+ const listRef = useRef(null);
+ const [topGradientOpacity, setTopGradientOpacity] = useState(0);
+ const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
+ const [editingId, setEditingId] = useState(null);
+ const [editingTitle, setEditingTitle] = useState('');
+
+ const handleScroll = useCallback(e => {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+ setTopGradientOpacity(Math.min(scrollTop / 50, 1));
+ const bottomDistance = scrollHeight - (scrollTop + clientHeight);
+ setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));
+ }, []);
+
+ const startEditing = (folder, e) => {
+ e.stopPropagation();
+ setEditingId(folder.id);
+ setEditingTitle(folder.title);
+ };
+
+ const confirmEdit = (id) => {
+ if (editingTitle.trim()) onRenameFolder(id, editingTitle.trim());
+ setEditingId(null);
+ };
+
+ const cancelEdit = () => setEditingId(null);
+
+ const folderHasPages = (folderId) => pages.some(p => p.folder.id === folderId);
+
+ return (
+
+
+ {folders.length === 0 && (
+
+ No folders yet
+
+ )}
+ {folders.map((folder, index) => (
+
{}}
+ onClick={() => { if (!editMode) onSelectFolder(folder); }}
+ >
+
+ {editMode && (
+
onToggleSelect(folder.id)}
+ onClick={e => e.stopPropagation()}
+ />
+ )}
+
+
+
+ {folderHasPages(folder.id) ? : }
+
+
+
+ {editingId === folder.id ? (
+ <>
+
setEditingTitle(e.target.value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter') confirmEdit(folder.id);
+ if (e.key === 'Escape') cancelEdit();
+ }}
+ onClick={e => e.stopPropagation()}
+ autoFocus
+ />
+
{ e.stopPropagation(); confirmEdit(folder.id); }}>✓
+
{ e.stopPropagation(); cancelEdit(); }}>✕
+ >
+ ) : (
+ <>
+
{folder.title}
+ {editMode && (
+
startEditing(folder, e)}>✏️
+ )}
+ {!editMode &&
› }
+ >
+ )}
+
+
+ ))}
+
+ {showGradients && (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+export default FolderPanel;
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/FolderServices.js b/src/front/components/pages-y-folder/FolderServices.js
new file mode 100644
index 0000000000..8ab257db12
--- /dev/null
+++ b/src/front/components/pages-y-folder/FolderServices.js
@@ -0,0 +1,44 @@
+const backend_url = import.meta.env.VITE_BACKEND_URL;
+
+export const getFolders = async () => {
+ const response = await fetch(`${backend_url}/api/folders/`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+export const createFolder = async (title) => {
+ const response = await fetch(`${backend_url}/api/folders/`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify({ title })
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+export const updateFolder = async (id, title) => {
+ const response = await fetch(`${backend_url}/api/folders/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify({ title })
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+export const deleteFolder = async (id) => {
+ const response = await fetch(`${backend_url}/api/folders/${id}`, {
+ method: "DELETE",
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/FoldersPage.css b/src/front/components/pages-y-folder/FoldersPage.css
new file mode 100644
index 0000000000..6efda18ef5
--- /dev/null
+++ b/src/front/components/pages-y-folder/FoldersPage.css
@@ -0,0 +1,414 @@
+.fp-page {
+ height: calc(100vh - 56px - 62px);
+ background: #E8EDE6;
+ display: flex;
+ flex-direction: column;
+ padding-top: 16px;
+}
+
+.fp-layout {
+ flex: 1;
+ display: flex;
+ gap: 16px;
+ padding: 0 16px 16px 16px;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+/* SIDEBAR */
+.fp-sidebar {
+ width: 260px;
+ min-width: 260px;
+ background: #F2F5F0;
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+}
+
+.fp-sidebar-header {
+ padding: 18px 20px;
+ border-bottom: 1px solid #B0BBA8;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-shrink: 0;
+}
+
+.fp-sidebar-title {
+ font-size: 15px;
+ font-weight: 500;
+ color: #1A1A1A;
+ letter-spacing: 0.02em;
+}
+
+.fp-sidebar-btns {
+ display: flex;
+ gap: 6px;
+}
+
+.fp-icon-btn {
+ width: 30px;
+ height: 30px;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s ease;
+}
+
+.fp-icon-btn.edit { background: rgba(45,58,74,0.08); color: #2D3A4A; }
+.fp-icon-btn.edit:hover { background: rgba(45,58,74,0.15); }
+.fp-icon-btn.del { background: rgba(220,38,38,0.08); color: #dc2626; }
+.fp-icon-btn.del:hover { background: rgba(220,38,38,0.15); }
+.fp-icon-btn:disabled { opacity: 0.3; cursor: not-allowed; }
+
+.fp-sidebar-body {
+ flex: 1;
+ overflow: hidden;
+}
+
+.fp-sidebar-footer {
+ padding: 12px;
+ border-top: 1px solid #B0BBA8;
+ flex-shrink: 0;
+}
+
+.fp-delete-selected {
+ width: 100%;
+ padding: 9px;
+ background: #dc2626;
+ border: none;
+ border-radius: 8px;
+ font-size: 13px;
+ color: white;
+ cursor: pointer;
+ font-weight: 500;
+ transition: opacity 0.2s ease;
+ margin-bottom: 8px;
+}
+
+.fp-delete-selected:hover { opacity: 0.85; }
+.fp-delete-selected:disabled { opacity: 0.4; cursor: not-allowed; }
+
+.fp-new-folder-btn {
+ width: 100%;
+ padding: 10px;
+ background: #2D3A4A;
+ border: none;
+ border-radius: 8px;
+ font-size: 13px;
+ color: white;
+ cursor: pointer;
+ font-weight: 500;
+ transition: opacity 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+}
+
+.fp-new-folder-btn:hover { opacity: 0.85; }
+
+/* MAIN */
+.fp-main {
+ flex: 1;
+ background: #F2F5F0;
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+}
+
+.fp-main-header {
+ padding: 18px 24px;
+ border-bottom: 1px solid #B0BBA8;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-shrink: 0;
+}
+
+.fp-main-title {
+ font-size: 16px;
+ font-weight: 500;
+ color: #1A1A1A;
+}
+
+.fp-new-page-btn {
+ padding: 9px 18px;
+ background: #2D3A4A;
+ border: none;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: white;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+}
+
+.fp-new-page-btn:hover { opacity: 0.85; }
+
+.fp-main-body {
+ flex: 1;
+ overflow: hidden;
+}
+
+/* MODALS */
+.fp-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.4);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ padding: 20px;
+ animation: fp-fade 0.15s ease;
+}
+
+@keyframes fp-fade {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.fp-modal {
+ background: #F2F5F0;
+ border-radius: 12px;
+ padding: 28px;
+ width: 100%;
+ max-width: 680px;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+ animation: fp-slide 0.2s ease;
+}
+
+@keyframes fp-slide {
+ from { opacity: 0; transform: translateY(16px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.fp-modal-title {
+ font-size: 18px;
+ font-weight: 500;
+ color: #1A1A1A;
+ margin-bottom: 20px;
+}
+
+.fp-modal.confirm { max-width: 360px; text-align: center; }
+.fp-modal.confirm .fp-modal-icon { font-size: 44px; margin-bottom: 10px; }
+.fp-modal.confirm p { font-size: 14px; color: #6B7280; line-height: 1.6; margin-bottom: 24px; }
+
+.fp-input-group { margin-bottom: 16px; }
+
+.fp-label {
+ font-size: 11px;
+ font-weight: 500;
+ color: #6B7280;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ margin-bottom: 6px;
+ display: block;
+}
+
+.fp-input {
+ width: 100%;
+ padding: 11px 14px;
+ border: 1px solid #B0BBA8;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #1A1A1A;
+ outline: none;
+ transition: border-color 0.2s ease;
+ background: white;
+}
+
+.fp-input:focus { border-color: #2D3A4A; }
+.fp-textarea { resize: vertical; min-height: 250px; line-height: 1.6; }
+
+.fp-select {
+ width: 100%;
+ padding: 11px 14px;
+ border: 1px solid #B0BBA8;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #1A1A1A;
+ background: white;
+ outline: none;
+ cursor: pointer;
+}
+
+.fp-modal-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ margin-top: 20px;
+}
+
+.fp-modal.confirm .fp-modal-actions { justify-content: center; }
+
+.fp-btn-cancel {
+ padding: 9px 18px;
+ background: transparent;
+ border: 1.5px solid #2D3A4A;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: #2D3A4A;
+ cursor: pointer;
+ transition: background 0.2s ease;
+}
+
+.fp-btn-cancel:hover { background: rgba(45,58,74,0.06); }
+
+.fp-btn-primary {
+ padding: 9px 20px;
+ background: #2D3A4A;
+ border: none;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: white;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+}
+
+.fp-btn-primary:hover { opacity: 0.85; }
+.fp-btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
+
+.fp-btn-danger {
+ padding: 9px 20px;
+ background: #dc2626;
+ border: none;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: white;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+}
+
+.fp-btn-danger:hover { opacity: 0.85; }
+
+/* PAGE DETAIL */
+.fp-detail {
+ background: #F2F5F0;
+ border-radius: 12px;
+ width: 100%;
+ max-width: 680px;
+ max-height: 80vh;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+ animation: fp-slide 0.2s ease;
+}
+
+.fp-detail-header {
+ padding: 24px 28px 18px;
+ border-bottom: 1px solid #B0BBA8;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.fp-detail-title {
+ font-size: 20px;
+ font-weight: 500;
+ color: #1A1A1A;
+}
+
+.fp-detail-close {
+ width: 32px;
+ height: 32px;
+ background: rgba(45,58,74,0.08);
+ border: none;
+ border-radius: 8px;
+ color: #6B7280;
+ cursor: pointer;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.2s ease;
+}
+
+.fp-detail-close:hover { background: rgba(45,58,74,0.15); color: #1A1A1A; }
+
+.fp-detail-body {
+ padding: 24px 28px;
+ overflow-y: auto;
+ flex: 1;
+}
+
+.fp-detail-content {
+ font-size: 15px;
+ color: #1A1A1A;
+ line-height: 1.8;
+ white-space: pre-wrap;
+}
+
+/* RESPONSIVE */
+@media (max-width: 768px) {
+ .fp-page {
+ height: auto;
+ min-height: calc(100vh - 56px - 62px);
+ padding-top: 8px;
+ }
+
+ .fp-layout {
+ flex-direction: column;
+ padding: 0 10px 10px;
+ overflow: auto;
+ height: auto;
+ }
+
+ .fp-sidebar {
+ width: 100%;
+ min-width: unset;
+ max-height: 300px;
+ }
+
+ .fp-main {
+ min-height: 400px;
+ }
+
+ .fp-modal {
+ padding: 20px 16px;
+ border-radius: 12px;
+ }
+
+ .fp-detail {
+ max-height: 90vh;
+ border-radius: 12px 12px 0 0;
+ }
+
+ .fp-overlay {
+ align-items: flex-end;
+ padding: 0;
+ }
+
+ .fp-detail-header {
+ padding: 16px 20px 12px;
+ }
+
+ .fp-detail-body {
+ padding: 16px 20px;
+ }
+
+ .fp-main-header {
+ padding: 14px 16px;
+ }
+
+ .fp-sidebar-header {
+ padding: 14px 16px;
+ }
+}
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/FoldersPage.jsx b/src/front/components/pages-y-folder/FoldersPage.jsx
new file mode 100644
index 0000000000..48508c75fc
--- /dev/null
+++ b/src/front/components/pages-y-folder/FoldersPage.jsx
@@ -0,0 +1,304 @@
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import FolderPanel from "./FolderPanel";
+import PagePanel from "./PagePanel";
+import { getPages, createPage, updatePage, deletePage } from "./PageServices";
+import { getFolders, createFolder, updateFolder, deleteFolder } from "./FolderServices";
+import "./FoldersPage.css"
+
+const FoldersPage = () => {
+ const navigate = useNavigate();
+ const [folders, setFolders] = useState([]);
+ const [pages, setPages] = useState([]);
+ const [activeFolder, setActiveFolder] = useState(null);
+ const [editMode, setEditMode] = useState(false);
+ const [selectedFolders, setSelectedFolders] = useState([]);
+ const [openPage, setOpenPage] = useState(null);
+ const [showCreateForm, setShowCreateForm] = useState(false);
+ const [showMobileMain, setShowMobileMain] = useState(false);
+
+ const [showFolderModal, setShowFolderModal] = useState(false);
+ const [confirmModal, setConfirmModal] = useState(null);
+ const [moveModal, setMoveModal] = useState(null);
+
+ const [newFolderTitle, setNewFolderTitle] = useState("");
+ const [moveTarget, setMoveTarget] = useState("");
+
+ useEffect(() => {
+ const loadFolders = async () => {
+ const data = await getFolders();
+ if (data) setFolders(data);
+ };
+ loadFolders();
+ }, []);
+
+ useEffect(() => {
+ if (!activeFolder) return;
+ const loadPages = async () => {
+ const data = await getPages(activeFolder.id);
+ if (data) {
+ setPages(prev => {
+ const filtered = prev.filter(p => p.folder.id !== activeFolder.id);
+ return [...filtered, ...data];
+ });
+ }
+ };
+ loadPages();
+ }, [activeFolder]);
+
+ const folderPages = activeFolder ? pages.filter(p => p.folder.id === activeFolder.id) : [];
+
+ const toggleSelect = (id) => {
+ setSelectedFolders(prev => prev.includes(id) ? prev.filter(f => f !== id) : [...prev, id]);
+ };
+
+ const handleSelectFolder = (folder) => {
+ setActiveFolder(folder);
+ setShowMobileMain(true);
+ };
+
+ const handleCreateFolder = async () => {
+ if (!newFolderTitle.trim()) return;
+ const data = await createFolder(newFolderTitle);
+ if (data) setFolders(prev => [...prev, data]);
+ setNewFolderTitle("");
+ setShowFolderModal(false);
+ };
+
+ const handleDeleteFolders = async () => {
+ for (const id of selectedFolders) await deleteFolder(id);
+ setFolders(prev => prev.filter(f => !selectedFolders.includes(f.id)));
+ setPages(prev => prev.filter(p => !selectedFolders.includes(p.folder.id)));
+ if (activeFolder && selectedFolders.includes(activeFolder.id)) setActiveFolder(null);
+ setSelectedFolders([]);
+ setEditMode(false);
+ setConfirmModal(null);
+ };
+
+ const handleRenameFolder = async (id, newTitle) => {
+ const data = await updateFolder(id, newTitle);
+ if (data) setFolders(prev => prev.map(f => f.id === id ? { ...f, title: newTitle } : f));
+ };
+
+ const handleCreatePage = async (title, content) => {
+ const data = await createPage(activeFolder.id, title, content);
+ if (data) setPages(prev => [...prev, data]);
+ setShowCreateForm(false);
+ };
+
+ const handleUpdatePage = async (id, title, content) => {
+ const data = await updatePage(id, title, content);
+ if (data) setPages(prev => prev.map(p => p.id === id ? { ...p, title, content } : p));
+ };
+
+ const handleDeletePage = async (page) => {
+ await deletePage(page.id);
+ setPages(prev => prev.filter(p => p.id !== page.id));
+ setConfirmModal(null);
+ };
+
+ const confirmLabel = selectedFolders.length === 1
+ ? `the folder "${folders.find(f => f.id === selectedFolders[0])?.title}"`
+ : `these ${selectedFolders.length} folders`;
+
+ return (
+ <>
+
+
+
+
+
+
+
+ navigate("/home")}
+ style={{ padding: '6px 12px', fontSize: '12px' }}
+ >← Home
+ Folders
+
+
+ { setEditMode(!editMode); setSelectedFolders([]); }}
+ >✏️
+ {editMode && (
+ setConfirmModal({ type: "folders" })}
+ >🗑️
+ )}
+
+
+
+
+
+
+
+
+ {editMode && (
+ setConfirmModal({ type: "folders" })}
+ >
+ Delete {selectedFolders.length > 0 ? `(${selectedFolders.length})` : "selected"}
+
+ )}
+ setShowFolderModal(true)}>
+ + New folder
+
+
+
+
+
+
+
+
+
+ setShowMobileMain(false)}
+ style={{ padding: '6px 12px', fontSize: '12px' }}
+ >← Folders
+
+ {activeFolder ? activeFolder.title : "Select a folder"}
+
+
+ {activeFolder && !showCreateForm && (
+
setShowCreateForm(true)}>
+ + New page
+
+ )}
+
+
+
{ setMoveModal(page); setMoveTarget(""); }}
+ onDeletePage={(page) => setConfirmModal({ type: "page", id: page.id, title: page.title })}
+ onCreatePage={() => setShowCreateForm(true)}
+ onUpdatePage={handleUpdatePage}
+ showCreateForm={showCreateForm}
+ onCancelCreate={() => setShowCreateForm(false)}
+ onSubmitCreate={handleCreatePage}
+ />
+
+
+
+
+
+
+ {openPage && (
+
setOpenPage(null)}>
+
e.stopPropagation()}>
+
+
{openPage.title}
+
setOpenPage(null)}>✕
+
+
+
+
+ )}
+
+
+ {showFolderModal && (
+
setShowFolderModal(false)}>
+
e.stopPropagation()}>
+
New folder
+
+ Name
+ setNewFolderTitle(e.target.value)}
+ onKeyDown={e => e.key === "Enter" && handleCreateFolder()}
+ autoFocus
+ />
+
+
+ setShowFolderModal(false)}>Cancel
+ Create
+
+
+
+ )}
+
+
+ {moveModal && (
+
setMoveModal(null)}>
+
e.stopPropagation()}>
+
Move to folder
+
+ Destination folder
+ setMoveTarget(e.target.value)}
+ >
+ -- Select --
+ {folders.filter(f => f.id !== moveModal.folder.id).map(f => (
+ {f.title}
+ ))}
+
+
+
+ setMoveModal(null)}>Cancel
+ {
+ const targetFolder = folders.find(f => f.id === parseInt(moveTarget));
+ setPages(prev => prev.map(p => p.id === moveModal.id
+ ? { ...p, folder: { id: targetFolder.id, title: targetFolder.title } }
+ : p
+ ));
+ setMoveModal(null);
+ setMoveTarget("");
+ }}>Move
+
+
+
+ )}
+
+
+ {confirmModal && (
+
setConfirmModal(null)}>
+
e.stopPropagation()}>
+
🗑️
+
Are you sure?
+
+ You are about to delete {confirmModal.type === "page"
+ ? `the page "${confirmModal.title}"`
+ : confirmLabel
+ }. This action cannot be undone.
+
+
+ setConfirmModal(null)}>Cancel
+ handleDeletePage({ id: confirmModal.id, title: confirmModal.title })
+ : handleDeleteFolders
+ }
+ >Yes, delete
+
+
+
+ )}
+ >
+ );
+};
+
+export default FoldersPage;
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/PagePanel.css b/src/front/components/pages-y-folder/PagePanel.css
new file mode 100644
index 0000000000..91f3729c55
--- /dev/null
+++ b/src/front/components/pages-y-folder/PagePanel.css
@@ -0,0 +1,272 @@
+.page-list-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.page-scroll-list {
+ height: 100%;
+ overflow-y: auto;
+ padding: 12px;
+}
+
+.page-scroll-list::-webkit-scrollbar { width: 4px; }
+.page-scroll-list::-webkit-scrollbar-track { background: transparent; }
+.page-scroll-list::-webkit-scrollbar-thumb { background: #B0BBA8; border-radius: 4px; }
+
+.page-animated-item {
+ padding: 14px 16px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ cursor: pointer;
+ transition: border-color 0.2s ease, background 0.2s ease;
+ margin-bottom: 6px;
+}
+
+.page-animated-item:hover { background-color: rgba(45,58,74,0.04); }
+.page-animated-item.selected { background-color: rgba(45,58,74,0.08); border-color: #B0BBA8; }
+
+.page-animated-item .item-title {
+ color: #1A1A1A;
+ margin: 0 0 4px 0;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.page-animated-item .item-preview {
+ color: #6B7280;
+ margin: 0;
+ font-size: 12px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.page-animated-item .item-actions {
+ display: flex;
+ gap: 6px;
+ margin-top: 8px;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+}
+
+.page-animated-item:hover .item-actions,
+.page-animated-item.selected .item-actions { opacity: 1; }
+
+.item-act-btn {
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ border: none;
+ cursor: pointer;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s ease;
+}
+
+.item-act-btn.edit { background: rgba(45,58,74,0.08); color: #2D3A4A; }
+.item-act-btn.edit:hover { background: rgba(45,58,74,0.15); }
+.item-act-btn.move { background: rgba(45,58,74,0.08); color: #2D3A4A; }
+.item-act-btn.move:hover { background: rgba(45,58,74,0.15); }
+.item-act-btn.del { background: rgba(220,38,38,0.08); color: #dc2626; }
+.item-act-btn.del:hover { background: rgba(220,38,38,0.15); }
+
+.page-top-gradient {
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 50px;
+ background: linear-gradient(to bottom, #F2F5F0, transparent);
+ pointer-events: none;
+ transition: opacity 0.3s ease;
+}
+
+.page-bottom-gradient {
+ position: absolute;
+ bottom: 0; left: 0; right: 0;
+ height: 80px;
+ background: linear-gradient(to top, #F2F5F0, transparent);
+ pointer-events: none;
+ transition: opacity 0.3s ease;
+}
+
+/* EMPTY STATE */
+.page-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ min-height: 300px;
+ gap: 12px;
+}
+
+.page-empty-icon { font-size: 48px; opacity: 0.35; }
+
+.page-empty-text {
+ font-size: 14px;
+ color: #6B7280;
+ font-weight: 400;
+}
+
+.page-empty-btn {
+ padding: 12px 28px;
+ background: #2D3A4A;
+ border: none;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ color: white;
+ cursor: pointer;
+ margin-top: 8px;
+ transition: opacity 0.2s ease;
+}
+
+.page-empty-btn:hover { opacity: 0.85; }
+
+/* INLINE CREATE / EDIT FORM */
+.page-create-form {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 20px 28px;
+ font-family: 'Inter', 'DM Sans', sans-serif;
+}
+
+.page-create-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: 1.1rem;
+ border-bottom: 1px solid #B0BBA8;
+ margin-bottom: 1.25rem;
+ flex-shrink: 0;
+}
+
+.page-create-title {
+ font-size: 0.78rem;
+ font-weight: 500;
+ letter-spacing: 0.12em;
+ text-transform: lowercase;
+ color: #6B7280;
+ margin: 0;
+}
+
+.page-create-close {
+ width: 28px;
+ height: 28px;
+ background: rgba(45,58,74,0.08);
+ border: none;
+ border-radius: 8px;
+ color: #6B7280;
+ cursor: pointer;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s ease;
+}
+.page-create-close:hover { background: rgba(45,58,74,0.15); color: #1A1A1A; }
+
+.page-create-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ overflow: hidden;
+}
+
+.page-create-label { display: none; }
+
+.page-create-input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: clamp(1.3rem, 2vw, 1.65rem);
+ font-weight: 700;
+ color: #1A1A1A;
+ font-family: inherit;
+ padding: 0 0 0.5rem 0;
+ margin-bottom: 0.6rem;
+ flex-shrink: 0;
+}
+.page-create-input::placeholder { color: #C8CEC4; font-weight: 400; }
+
+.page-create-textarea {
+ flex: 1;
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: clamp(0.88rem, 1.1vw, 1rem);
+ color: #1A1A1A;
+ font-family: inherit;
+ line-height: 1.75;
+ resize: none;
+ min-height: 0;
+}
+.page-create-textarea::placeholder { color: #C8CEC4; }
+
+.page-create-footer {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ padding-top: 16px;
+ border-top: 1px solid #B0BBA8;
+ flex-shrink: 0;
+}
+
+.page-create-btn-cancel {
+ padding: 6px 16px;
+ border-radius: 8px;
+ border: 1.5px solid #2D3A4A;
+ background: transparent;
+ color: #2D3A4A;
+ font-family: inherit;
+ font-size: 0.76rem;
+ text-transform: lowercase;
+ cursor: pointer;
+ transition: background 0.2s;
+ letter-spacing: 0.04em;
+}
+.page-create-btn-cancel:hover { background: rgba(45,58,74,0.07); }
+
+.page-create-btn-submit {
+ padding: 6px 16px;
+ border-radius: 8px;
+ border: none;
+ background: #2D3A4A;
+ color: #fff;
+ font-family: inherit;
+ font-size: 0.76rem;
+ text-transform: lowercase;
+ cursor: pointer;
+ transition: opacity 0.2s;
+ letter-spacing: 0.04em;
+}
+.page-create-btn-submit:hover { opacity: 0.85; }
+/* RESPONSIVE */
+@media (max-width: 768px) {
+ .page-scroll-list {
+ padding: 8px;
+ }
+
+ .page-create-form {
+ padding: 14px 16px;
+ }
+
+ .page-animated-item .item-actions {
+ opacity: 1;
+ }
+
+ .page-empty {
+ min-height: 200px;
+ }
+
+ .page-create-footer {
+ padding-top: 12px;
+ }
+}
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/PagePanel.jsx b/src/front/components/pages-y-folder/PagePanel.jsx
new file mode 100644
index 0000000000..9477e67bd5
--- /dev/null
+++ b/src/front/components/pages-y-folder/PagePanel.jsx
@@ -0,0 +1,210 @@
+import { useRef, useState, useCallback } from 'react';
+import { motion, useInView } from 'motion/react';
+import './PagePanel.css';
+
+const AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {
+ const ref = useRef(null);
+ const inView = useInView(ref, { amount: 0.5, triggerOnce: false });
+ return (
+
+ {children}
+
+ );
+};
+
+const PagePanel = ({
+ pages = [],
+ activeFolder = null,
+ onOpenPage,
+ onMovePage,
+ onDeletePage,
+ onCreatePage,
+ onUpdatePage,
+ showCreateForm = false,
+ onCancelCreate,
+ onSubmitCreate,
+ showGradients = true,
+ displayScrollbar = true,
+}) => {
+ const listRef = useRef(null);
+ const [selectedIndex, setSelectedIndex] = useState(-1);
+ const [topGradientOpacity, setTopGradientOpacity] = useState(0);
+ const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
+ const [newTitle, setNewTitle] = useState('');
+ const [newContent, setNewContent] = useState('');
+ const [editingPage, setEditingPage] = useState(null);
+ const [editTitle, setEditTitle] = useState('');
+ const [editContent, setEditContent] = useState('');
+
+ const folderPages = activeFolder ? pages.filter(p => p.folder.id === activeFolder.id) : [];
+
+ const handleScroll = useCallback(e => {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+ setTopGradientOpacity(Math.min(scrollTop / 50, 1));
+ const bottomDistance = scrollHeight - (scrollTop + clientHeight);
+ setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));
+ }, []);
+
+ const handleSubmitCreate = () => {
+ if (!newTitle.trim() || !newContent.trim()) return;
+ onSubmitCreate(newTitle.trim(), newContent.trim());
+ setNewTitle('');
+ setNewContent('');
+ };
+
+ const handleCancelCreate = () => {
+ setNewTitle('');
+ setNewContent('');
+ onCancelCreate();
+ };
+
+ const handleStartEdit = (page, e) => {
+ e.stopPropagation();
+ setEditingPage(page);
+ setEditTitle(page.title);
+ setEditContent(page.content);
+ };
+
+ const handleSubmitEdit = () => {
+ if (!editTitle.trim() || !editContent.trim()) return;
+ onUpdatePage(editingPage.id, editTitle.trim(), editContent.trim());
+ setEditingPage(null);
+ };
+
+ const handleCancelEdit = () => {
+ setEditingPage(null);
+ setEditTitle('');
+ setEditContent('');
+ };
+
+ if (editingPage) return (
+
+
+ Edit page
+ ✕
+
+
+ Title
+ setEditTitle(e.target.value)}
+ autoFocus
+ />
+ Contenido
+
+
+ Cancel
+ Save changes
+
+
+ );
+
+ if (showCreateForm) return (
+
+
+ New page
+ ✕
+
+
+ Title
+ setNewTitle(e.target.value)}
+ autoFocus
+ />
+ Contenido
+
+
+ Cancel
+ Create page
+
+
+ );
+
+ if (!activeFolder) return (
+
+
+
Select a folder to see its pages
+
+ );
+
+ if (folderPages.length === 0) return (
+
+
+
No pages in this folder
+
+ Create new page or note
+
+ );
+
+ return (
+
+
+ {folderPages.map((page, index) => (
+
setSelectedIndex(index)}
+ onClick={() => { setSelectedIndex(index); onOpenPage(page); }}
+ >
+
+
{page.title}
+
{page.content}
+
e.stopPropagation()}>
+ handleStartEdit(page, e)} title="Edit page">✏️
+ onMovePage(page)} title="Move to folder">📂
+ onDeletePage(page)} title="Delete page">✕
+
+
+
+ ))}
+
+ {showGradients && (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+export default PagePanel;
\ No newline at end of file
diff --git a/src/front/components/pages-y-folder/PageServices.js b/src/front/components/pages-y-folder/PageServices.js
new file mode 100644
index 0000000000..6696c9867e
--- /dev/null
+++ b/src/front/components/pages-y-folder/PageServices.js
@@ -0,0 +1,48 @@
+const backend_url = import.meta.env.VITE_BACKEND_URL;
+
+export const getPages = async (folderId) => {
+ const response = await fetch(`${backend_url}/api/folders/${folderId}/pages`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+export const createPage = async (folderId, title, content) => {
+ const response = await fetch(`${backend_url}/api/folders/${folderId}/pages`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify({ title, content })
+ });
+ if (!response.ok) {
+ const err = await response.json();
+ console.error("❌ createPage error:", err);
+ return false;
+ }
+ return await response.json();
+};
+
+export const updatePage = async (id, title, content) => {
+ const response = await fetch(`${backend_url}/api/pages/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${localStorage.getItem("token")}`
+ },
+ body: JSON.stringify({ title, content })
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
+
+export const deletePage = async (id) => {
+ const response = await fetch(`${backend_url}/api/pages/${id}`, {
+ method: "DELETE",
+ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
+ });
+ if (!response.ok) return false;
+ return await response.json();
+};
\ No newline at end of file
diff --git a/src/front/hooks/useGlobalReducer.jsx b/src/front/hooks/useGlobalReducer.jsx
index 6aeb9d768e..44778b96ee 100644
--- a/src/front/hooks/useGlobalReducer.jsx
+++ b/src/front/hooks/useGlobalReducer.jsx
@@ -1,24 +1,131 @@
-// Import necessary hooks and functions from React.
-import { useContext, useReducer, createContext } from "react";
-import storeReducer, { initialStore } from "../store" // Import the reducer and the initial state.
+import { useContext, useReducer, createContext, useEffect, useRef, useCallback } from "react";
+import storeReducer, { initialStore } from "../store";
-// Create a context to hold the global state of the application
-// We will call this global state the "store" to avoid confusion while using local states
-const StoreContext = createContext()
+const StoreContext = createContext();
+
+const POMODORO_KEY = "pomify_pomodoro";
+
+const loadPomodoro = () => {
+ try {
+ const saved = sessionStorage.getItem(POMODORO_KEY);
+ return saved ? JSON.parse(saved) : null;
+ } catch {
+ return null;
+ }
+};
+
+const savePomodoro = (pomodoro) => {
+ try {
+ sessionStorage.setItem(POMODORO_KEY, JSON.stringify(pomodoro));
+ } catch {}
+};
+
+const getInitialStore = () => {
+ const base = initialStore();
+ const savedPomodoro = loadPomodoro();
+ if (savedPomodoro) {
+ return { ...base, pomodoro: { ...savedPomodoro, isRunning: false } };
+ }
+ return base;
+};
+
+const persistingReducer = (store, action) => {
+ const next = storeReducer(store, action);
+ if (next.pomodoro !== store.pomodoro) {
+ savePomodoro(next.pomodoro);
+ }
+ return next;
+};
+
+const playBreakAlarm = () => {
+ try {
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
+ const notes = [523.25, 659.25, 783.99];
+ notes.forEach((freq, i) => {
+ const osc = ctx.createOscillator();
+ const gain = ctx.createGain();
+ osc.connect(gain);
+ gain.connect(ctx.destination);
+ osc.type = "sine";
+ osc.frequency.value = freq;
+ const start = ctx.currentTime + i * 0.38;
+ const end = start + 0.32;
+ gain.gain.setValueAtTime(0, start);
+ gain.gain.linearRampToValueAtTime(0.6, start + 0.04);
+ gain.gain.linearRampToValueAtTime(0, end);
+ osc.start(start);
+ osc.stop(end + 0.05);
+ });
+ setTimeout(() => ctx.close(), 2000);
+ } catch {}
+};
-// Define a provider component that encapsulates the store and warps it in a context provider to
-// broadcast the information throught all the app pages and components.
export function StoreProvider({ children }) {
- // Initialize reducer with the initial state.
- const [store, dispatch] = useReducer(storeReducer, initialStore())
- // Provide the store and dispatch method to all child components.
- return
- {children}
-
+ const [store, dispatch] = useReducer(persistingReducer, getInitialStore());
+ const phaseLeftRef = useRef(store.pomodoro.phaseLeft);
+ const storeRef = useRef(store);
+ const audioRef = useRef(null);
+
+ useEffect(() => {
+ storeRef.current = store;
+ phaseLeftRef.current = store.pomodoro.phaseLeft;
+ }, [store]);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ if (!storeRef.current.pomodoro.isRunning) return;
+ dispatch({ type: "pomodoro_tick" });
+ if (phaseLeftRef.current <= 1) {
+ playBreakAlarm();
+ dispatch({ type: "pomodoro_phase_end" });
+ }
+ }, 1000);
+ return () => clearInterval(interval);
+ }, []);
+
+ useEffect(() => {
+ const { currentPlaylist, currentTrackIndex } = storeRef.current;
+ if (!currentPlaylist || !audioRef.current) return;
+ const sound = currentPlaylist.sounds[currentTrackIndex];
+ const preview = sound?.previews?.["preview-lq-mp3"] || sound?.previews?.["preview-hq-mp3"] || sound?.previews?.["preview-lq-ogg"];
+ if (preview) {
+ audioRef.current.src = preview;
+ audioRef.current.load();
+ audioRef.current.play().catch(() => {});
+ dispatch({ type: "set_playing", payload: true });
+ }
+ }, [store.currentPlaylist, store.currentTrackIndex]);
+
+ useEffect(() => {
+ if (!audioRef.current) return;
+ if (store.isPlaying) {
+ audioRef.current.play().catch(() => {});
+ } else {
+ audioRef.current.pause();
+ }
+ }, [store.isPlaying]);
+
+ return (
+
+ {
+ const { currentPlaylist, currentTrackIndex } = storeRef.current;
+ if (!currentPlaylist) return;
+ if (currentTrackIndex < currentPlaylist.sounds.length - 1) {
+ dispatch({ type: "set_track_index", payload: currentTrackIndex + 1 });
+ } else {
+ dispatch({ type: "set_playing", payload: false });
+ }
+ }}
+ style={{ display: "none" }}
+ />
+ {children}
+
+ );
}
-// Custom hook to access the global state and dispatch function.
export default function useGlobalReducer() {
- const { dispatch, store } = useContext(StoreContext)
- return { dispatch, store };
+ const { dispatch, store, audioRef } = useContext(StoreContext);
+ return { dispatch, store, audioRef };
}
\ No newline at end of file
diff --git a/src/front/pages/API-externa/Freesound.jsx b/src/front/pages/API-externa/Freesound.jsx
new file mode 100644
index 0000000000..9e05c7f2cc
--- /dev/null
+++ b/src/front/pages/API-externa/Freesound.jsx
@@ -0,0 +1,162 @@
+import React, { useEffect, useState } from "react";
+import { searchSounds } from "./freesound";
+import { useNavigate } from "react-router-dom";
+import "./freesound.css";
+import useGlobalReducer from "../../hooks/useGlobalReducer";
+
+export const SoundList = () => {
+ const [playlistsData, setPlaylistsData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState("");
+ const [selectedPlaylist, setSelectedPlaylist] = useState(null);
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const { dispatch } = useGlobalReducer();
+ const navigate = useNavigate();
+
+ const playlists = [
+ { name: "Relax", query: "relax ambient meditation calm", coverImage: "https://images.pexels.com/photos/3822622/pexels-photo-3822622.jpeg" },
+ { name: "Truenos", query: "thunder", coverImage: "https://images.pexels.com/photos/326055/pexels-photo-326055.jpeg" },
+ { name: "Viento", query: "wind", coverImage: "https://images.pexels.com/photos/459225/pexels-photo-459225.jpeg" },
+ { name: "Mar", query: "ocean", coverImage: "https://images.pexels.com/photos/533923/pexels-photo-533923.jpeg" },
+ { name: "Bosque", query: "forest", coverImage: "https://images.pexels.com/photos/15286/pexels-photo.jpg" },
+ { name: "Ciudad", query: "city ambience", coverImage: "https://images.pexels.com/photos/373965/pexels-photo-373965.jpeg" }
+ ];
+
+ useEffect(() => {
+ const loadPlaylists = async () => {
+ try {
+ const results = await Promise.all(
+ playlists.map(async (pl) => {
+ const sounds = await searchSounds(pl.query);
+ const filtered = sounds
+ .filter(s => s.previews?.["preview-lq-mp3"] || s.previews?.["preview-hq-mp3"] || s.previews?.["preview-lq-ogg"])
+ .slice(0, 25);
+ return { ...pl, sounds: filtered };
+ })
+ );
+ setPlaylistsData(results);
+ } catch (err) {
+ console.error(err);
+ setError("No se pudieron cargar las playlists");
+ }
+ setLoading(false);
+ };
+ loadPlaylists();
+ }, []);
+
+ const getPreview = (sound) => {
+ return sound.previews?.["preview-lq-mp3"] || sound.previews?.["preview-hq-mp3"] || sound.previews?.["preview-lq-ogg"];
+ };
+
+ const openModal = (pl) => {
+ setSelectedPlaylist(pl);
+ setTimeout(() => setModalVisible(true), 10);
+ };
+
+ const closeModal = () => {
+ setModalVisible(false);
+ setTimeout(() => setSelectedPlaylist(null), 350);
+ };
+
+ const handleSelect = (pl) => {
+ dispatch({ type: "set_playlist", payload: pl });
+ navigate("/home");
+ };
+
+ const handlePlayInPlace = (pl) => {
+ dispatch({ type: "set_playlist", payload: pl });
+ };
+
+ if (loading) return
Cargando playlists...
;
+ if (error) return
{error}
;
+
+ return (
+ <>
+
navigate("/home")}
+ style={{
+ margin: "1rem 0 0 1rem",
+ padding: "8px 16px",
+ borderRadius: "8px",
+ border: "1.5px solid var(--color-divider)",
+ background: "transparent",
+ color: "var(--color-text-primary)",
+ cursor: "pointer",
+ fontSize: "0.9rem"
+ }}
+ >
+ ← Back to home
+
+
+
+
Sound Playlists
+
+
+ {playlistsData.map((pl) => (
+
+
+
+
+
handlePlayInPlace(pl)}>▶
+
{pl.name}
+
+
+ openModal(pl)}
+ >
+ Ver
+
+ handleSelect(pl)}
+ >
+ Seleccionar
+
+
+
+
+ ))}
+
+
+ {selectedPlaylist && (
+
+
e.stopPropagation()}
+ >
+
✕
+
{selectedPlaylist.name}
+
+ {selectedPlaylist.sounds.map((sound, index) => {
+ const previewUrl = getPreview(sound);
+ if (!previewUrl) return null;
+ return (
+
+
{sound.name}
+
{
+ document.querySelectorAll(".modal-sounds audio").forEach(a => {
+ if (a !== e.target) { a.pause(); a.currentTime = 0; }
+ });
+ }}>
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/front/pages/API-externa/freesound.css b/src/front/pages/API-externa/freesound.css
new file mode 100644
index 0000000000..38bab48238
--- /dev/null
+++ b/src/front/pages/API-externa/freesound.css
@@ -0,0 +1,319 @@
+:root {
+ --color-bg: #E8EDE6;
+ --color-surface: #F2F5F0;
+ --color-text-primary: #1A1A1A;
+ --color-text-secondary: #6B7280;
+ --color-btn-primary-bg: #2D3A4A;
+ --color-btn-primary-text: #FFFFFF;
+ --color-btn-outline-border: #2D3A4A;
+ --color-btn-outline-text: #2D3A4A;
+ --color-divider: #B0BBA8;
+}
+
+body {
+ background-color: var(--color-bg);
+ font-family: "Inter", "DM Sans", system-ui, sans-serif;
+ color: var(--color-text-primary);
+ margin: 0;
+ padding: 0;
+}
+
+/* PLAYLIST PAGE */
+.sound-playlists {
+ margin-top: 1rem;
+ max-width: 1100px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 0 1.5rem 1rem;
+}
+
+.sound-playlists h1 {
+ text-align: center;
+ margin-bottom: 1rem;
+ color: var(--color-text-primary);
+ font-weight: 900;
+ font-size: clamp(1.6rem, 3vw, 2.2rem);
+ letter-spacing: -0.02em;
+}
+
+/* PLAYLIST CARD */
+.playlist-card {
+ height: 100%;
+ background: var(--color-surface);
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+ border-radius: 10px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.playlist-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+}
+
+.playlist-card img {
+ height: 130px;
+ object-fit: cover;
+ width: 100%;
+ border-bottom: 1px solid var(--color-divider);
+}
+
+.playlist-card .overlay {
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ background: var(--color-btn-primary-bg);
+ color: var(--color-btn-primary-text);
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ font-weight: 500;
+}
+
+.playlist-card .card-body {
+ background: var(--color-surface);
+ padding: 0.6rem;
+ display: flex;
+ gap: 6px;
+ flex-wrap: wrap;
+}
+
+.playlist-card audio {
+ width: 100%;
+ margin-bottom: 0.5rem;
+ border-radius: 6px;
+}
+
+/* BOTONES */
+.btn-primary {
+ background-color: var(--color-btn-primary-bg);
+ color: var(--color-btn-primary-text);
+ border-radius: 8px;
+ padding: 8px 16px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ border: none;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+}
+
+.btn-primary:hover {
+ opacity: 0.85;
+}
+
+.btn-outline {
+ background: transparent;
+ color: var(--color-btn-outline-text);
+ border: 1.5px solid var(--color-btn-outline-border);
+ border-radius: 8px;
+ padding: 8px 16px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.2s ease;
+}
+
+.btn-outline:hover {
+ background: rgba(45, 58, 74, 0.06);
+}
+
+.playlist-card button {
+ flex: 1;
+}
+
+/* PLAY BUTTON */
+.playlist-image-container {
+ position: relative;
+}
+
+.play-button {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0,0,0,0.35);
+ border: none;
+ color: var(--color-btn-primary-text);
+ font-size: 36px;
+ cursor: pointer;
+ opacity: 1;
+ border-radius: 50%;
+ width: 56px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s ease, transform 0.2s ease;
+}
+
+.playlist-image-container:hover img {
+ filter: brightness(80%);
+}
+
+.playlist-image-container:hover .play-button {
+ background: rgba(0,0,0,0.55);
+ transform: translate(-50%, -50%) scale(1.1);
+}
+
+/* OVERLAY */
+.sound-modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(26, 26, 26, 0.6);
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ z-index: 999;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.sound-modal-overlay.visible {
+ opacity: 1;
+}
+
+/* MODAL */
+.modal-playlist {
+ background: var(--color-surface);
+ border-radius: 16px 16px 0 0;
+ width: 100%;
+ max-width: 640px;
+ max-height: 75vh;
+ overflow-y: auto;
+ position: relative;
+ padding: 2rem;
+ transform: translateY(100%);
+ transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1);
+}
+
+.modal-playlist.visible {
+ transform: translateY(0);
+}
+
+.modal-playlist h2 {
+ margin: 0 0 1.5rem;
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--color-text-primary);
+}
+
+/* LISTA DE SONIDOS */
+.modal-sounds {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.modal-sound-item {
+ background: var(--color-bg);
+ border-radius: 10px;
+ padding: 12px 14px;
+ opacity: 0;
+ transform: translateY(12px);
+ animation: itemFadeIn 0.3s ease forwards;
+}
+
+@keyframes itemFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.modal-sound-item p {
+ margin: 0 0 8px;
+ font-size: 0.9rem;
+ color: var(--color-text-primary);
+ font-weight: 500;
+}
+
+.modal-sound-item audio {
+ width: 100%;
+ border-radius: 6px;
+}
+
+/* CIERRE MODAL */
+.close-modal {
+ position: absolute;
+ top: 14px;
+ right: 14px;
+ border: none;
+ background: var(--color-btn-primary-bg);
+ color: var(--color-btn-primary-text);
+ padding: 6px 10px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.85rem;
+}
+
+/* GLOBAL PLAYER */
+.global-player {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background: var(--color-btn-primary-bg);
+ padding: 10px 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.global-player audio {
+ width: 600px;
+}
+
+.player-btn {
+ background: var(--color-surface);
+ border: none;
+ font-size: 20px;
+ padding: 8px 12px;
+ margin: 0 10px;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.player-btn:hover {
+ background: rgba(26, 26, 26, 0.08);
+}
+/* RESPONSIVE */
+@media (max-width: 768px) {
+ .sound-playlists {
+ padding: 0 1rem 1rem;
+ margin-top: 1rem;
+ }
+
+ .sound-playlists h1 {
+ font-size: 1.8rem;
+ margin-bottom: 1rem;
+ }
+
+ .playlist-card img {
+ height: 130px;
+ }
+
+ .playlist-card .card-body {
+ padding: 0.5rem;
+ gap: 6px;
+ }
+
+ .btn-primary,
+ .btn-outline {
+ padding: 8px 10px;
+ font-size: 0.8rem;
+ }
+
+ .play-button {
+ width: 40px;
+ height: 40px;
+ font-size: 24px;
+ }
+}
\ No newline at end of file
diff --git a/src/front/pages/API-externa/freesound.js b/src/front/pages/API-externa/freesound.js
new file mode 100644
index 0000000000..941d0fe02a
--- /dev/null
+++ b/src/front/pages/API-externa/freesound.js
@@ -0,0 +1,10 @@
+export const searchSounds = async (query) => {
+ const url = `https://freesound.org/apiv2/search/text/?query=${query}&fields=id,name,previews`
+ const response = await fetch(url, {
+ headers: { Authorization: `Token ${import.meta.env.VITE_FREESOUND_API_KEY}` }
+ })
+ if (!response.ok) throw new Error("Error fetching sounds")
+ const data = await response.json()
+ console.log("Freesound API response:", data) // <- para debug
+ return data.results
+}
\ No newline at end of file
diff --git a/src/front/pages/AboutPage.jsx b/src/front/pages/AboutPage.jsx
new file mode 100644
index 0000000000..dff0631701
--- /dev/null
+++ b/src/front/pages/AboutPage.jsx
@@ -0,0 +1,171 @@
+import { useNavigate } from "react-router-dom";
+import "../styles/aboutPage.css"
+
+
+const features = [
+ {
+ badge: "Focus",
+ icon: "⏱",
+ title: "Pomodoro Timer",
+ description:
+ "Work in focused sprints and short breaks using the proven Pomodoro technique. Pomify keeps track of your sessions so you stay in flow without burning out. Customize your work and break intervals to match your rhythm.",
+ image: "/screenshots/pomodoro-zone.png",
+ alt: "Pomify Pomodoro timer interface",
+ },
+ {
+ badge: "Notes",
+ icon: "📝",
+ title: "Pages & Notes",
+ description:
+ "Write, edit, and organise your notes while you work — no context switching. Keep ideas, meeting notes, or research right next to your timer inside collapsible pages so nothing slips through the cracks.",
+ image: "/screenshots/pages-zone.png",
+ alt: "Pomify notes and pages interface",
+ reverse: true,
+ },
+ {
+ badge: "Playlists",
+ icon: "🎵",
+ title: "Ambient Sounds",
+ description:
+ "Pick a curated soundscape — rain, forest, ocean, café, lo-fi — and let it fade into the background while you work. The music player lives in the app so you never have to leave your flow to change the track.",
+ image: "/screenshots/music-zone.png",
+ alt: "Pomify ambient sounds and music player",
+ },
+ {
+ badge: "Folders",
+ icon: "📁",
+ title: "Folders",
+ description:
+ "Group your pages and notes into folders to keep your workspace tidy. Whether you organise by project, subject, or client — Pomify gives you the structure so you can focus on the work, not the filing.",
+ image: "/screenshots/folders-zone.png",
+ alt: "Pomify folders and organisation interface",
+ reverse: true,
+ },
+ {
+ badge: "Goals",
+ icon: "🎯",
+ title: "Your Goals",
+ description:
+ "Create your goals and keep them moving by updating their status to urgent, in progress, or done. Add new ones or remove them whenever you like. Best of all, your goals are always in sight, easily accessible from the navbar.",
+ image: "/screenshots/goals-zone.png",
+ alt: "Pomify folders and organisation interface",
+ reverse: true,
+ },
+];
+
+export const AboutPage = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
navigate("/home")}
+ style={{
+ padding: "8px 16px",
+ borderRadius: "8px",
+ border: "1.5px solid var(--color-divider)",
+ background: "transparent",
+ color: "var(--color-text-primary)",
+ cursor: "pointer",
+ fontSize: "0.9rem",
+ marginBottom: "2rem",
+ display: "inline-block"
+ }}
+ >← Home
+
+ {/* Hero */}
+
+
POMIFY
+
+ A productivity app designed to help you focus, organize your work, and achieve your goals — all in one place.
+
+
+
+
+
+ {features.map((feature, index) => (
+
+ {/* Screenshot */}
+ {feature.image ? (
+
+
{
+ // If image not found, swap to placeholder
+ e.currentTarget.parentElement.style.display = "none";
+ e.currentTarget.parentElement.nextSibling?.style
+ ? null
+ : null;
+ }}
+ />
+
+ ) : (
+
+ {feature.icon}
+
+ )}
+
+
+
{feature.badge}
+
{feature.title}
+
{feature.description}
+
+
+ ))}
+
+
+ navigate("/home")}>
+ Go to the app →
+
+
+
+ {/* Team */}
+
+
Built by
+
Dennielys · Messen · Juan
+
© 2026 Pomify — All rights reserved
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/pages/Demo.jsx b/src/front/pages/Demo.jsx
index 34250a45b7..0d9c7fb597 100644
--- a/src/front/pages/Demo.jsx
+++ b/src/front/pages/Demo.jsx
@@ -9,7 +9,7 @@ export const Demo = () => {
return (