diff --git a/README.es.md b/README.es.md index 73a220f..502ed88 100644 --- a/README.es.md +++ b/README.es.md @@ -1,3 +1,10 @@ +#PROYECTO FINAL - FINQUEST + +KANBAN +https://trello.com/b/2KYME95B/finquest + + + # Plantilla de WebApp con React JS y Flask API Construye aplicaciones web usando React.js para el front end y python/flask para tu API backend. diff --git a/README.md b/README.md index 39a7a35..b098722 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +#PROYECTO FINAL - FINQUEST + +KANBAN https://trello.com/b/2KYME95B/finquest + # JWT Example: React + Flask Ejemplo full stack de autenticación JWT con catálogo público y páginas privadas. diff --git a/migrations/versions/30a7f56653b6_.py b/migrations/versions/30a7f56653b6_.py new file mode 100644 index 0000000..841e0ac --- /dev/null +++ b/migrations/versions/30a7f56653b6_.py @@ -0,0 +1,111 @@ +"""empty message + +Revision ID: 30a7f56653b6 +Revises: +Create Date: 2026-04-07 19:07:29.637553 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '30a7f56653b6' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('products', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('slug', sa.String(length=120), nullable=False), + sa.Column('description', sa.Text(), nullable=False), + sa.Column('category', sa.String(length=80), nullable=False), + sa.Column('image_url', sa.String(length=255), nullable=False), + sa.Column('price', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('slug') + ) + 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(length=255), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('child', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('age', sa.Integer(), nullable=False), + sa.Column('pin', sa.String(length=4), nullable=False), + sa.Column('avatar', sa.String(length=255), nullable=True), + sa.Column('total_coins', sa.Integer(), nullable=False), + sa.Column('parent_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['parent_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('orders', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('product_id', sa.Integer(), nullable=False), + sa.Column('quantity', sa.Integer(), nullable=False), + sa.Column('status', sa.String(length=50), nullable=False), + sa.Column('unit_price', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['product_id'], ['products.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('grand_prize', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('coins', sa.Integer(), nullable=False), + sa.Column('image_url', sa.String(length=255), nullable=True), + sa.Column('child_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['child_id'], ['child.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('reward', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('cost', sa.Integer(), nullable=False), + sa.Column('child_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['child_id'], ['child.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('small_goal', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('coins', sa.Integer(), nullable=False), + sa.Column('child_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['child_id'], ['child.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('coins', sa.Integer(), nullable=False), + sa.Column('days', sa.String(length=100), nullable=False), + sa.Column('child_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['child_id'], ['child.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task') + op.drop_table('small_goal') + op.drop_table('reward') + op.drop_table('grand_prize') + op.drop_table('orders') + op.drop_table('child') + op.drop_table('user') + op.drop_table('products') + # ### end Alembic commands ### diff --git a/migrations/versions/7579f63830e1_.py b/migrations/versions/7579f63830e1_.py deleted file mode 100644 index b090ceb..0000000 --- a/migrations/versions/7579f63830e1_.py +++ /dev/null @@ -1,36 +0,0 @@ -"""empty message - -Revision ID: 7579f63830e1 -Revises: -Create Date: 2026-03-11 18:27:20.047787 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7579f63830e1' -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('name', 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/bd2f3a0b9b6d_add_products_and_orders.py b/migrations/versions/bd2f3a0b9b6d_add_products_and_orders.py deleted file mode 100644 index 8f3e637..0000000 --- a/migrations/versions/bd2f3a0b9b6d_add_products_and_orders.py +++ /dev/null @@ -1,50 +0,0 @@ -"""add products and orders - -Revision ID: bd2f3a0b9b6d -Revises: 7579f63830e1 -Create Date: 2026-03-13 15:30:00.000000 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'bd2f3a0b9b6d' -down_revision = '7579f63830e1' -branch_labels = None -depends_on = None - - -def upgrade(): - op.create_table( - 'products', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=120), nullable=False), - sa.Column('slug', sa.String(length=120), nullable=False), - sa.Column('description', sa.Text(), nullable=False), - sa.Column('category', sa.String(length=80), nullable=False), - sa.Column('image_url', sa.String(length=255), nullable=False), - sa.Column('price', sa.Numeric(precision=10, scale=2), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('slug') - ) - op.create_table( - 'orders', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('product_id', sa.Integer(), nullable=False), - sa.Column('quantity', sa.Integer(), nullable=False), - sa.Column('status', sa.String(length=50), nullable=False), - sa.Column('unit_price', sa.Numeric(precision=10, scale=2), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(['product_id'], ['products.id']), - sa.ForeignKeyConstraint(['user_id'], ['user.id']), - sa.PrimaryKeyConstraint('id') - ) - - -def downgrade(): - op.drop_table('orders') - op.drop_table('products') diff --git a/package-lock.json b/package-lock.json index 398b3c0..42d9a47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "vite": "^8.0.3" }, "engines": { "node": ">=20.0.0" @@ -43,15 +43,15 @@ } }, "node_modules/@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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -73,6 +73,7 @@ "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -199,9 +200,9 @@ } }, "node_modules/@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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -209,9 +210,9 @@ } }, "node_modules/@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==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -229,27 +230,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@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==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -291,15 +292,15 @@ } }, "node_modules/@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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -325,391 +326,28 @@ } }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@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==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@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==", - "cpu": [ - "x64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -890,53 +528,355 @@ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@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==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@napi-rs/wasm-runtime": "^1.1.1" }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/@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==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@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==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=14.0.0" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@types/babel__core": { @@ -997,6 +937,7 @@ "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1045,6 +986,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1063,10 +1005,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1244,10 +1187,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1273,6 +1217,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -1508,6 +1453,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1713,44 +1668,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@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" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1768,6 +1685,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2211,6 +2129,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2237,10 +2173,11 @@ } }, "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.4", @@ -2265,11 +2202,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" @@ -3007,9 +2945,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3070,18 +3008,279 @@ "node": ">=4.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lodash.merge": { @@ -3112,9 +3311,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3131,9 +3330,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3363,6 +3562,20 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/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", @@ -3374,9 +3587,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -3394,7 +3607,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3457,6 +3670,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3469,6 +3683,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3493,12 +3708,12 @@ } }, "node_modules/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==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.22.0" + "@remix-run/router": "1.23.2" }, "engines": { "node": ">=14.0.0" @@ -3508,13 +3723,13 @@ } }, "node_modules/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==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.22.0", - "react-router": "6.29.0" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" }, "engines": { "node": ">=14.0.0" @@ -3602,21 +3817,38 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "dev": true, "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" } }, "node_modules/run-parallel": { @@ -4028,6 +4260,31 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4192,50 +4449,67 @@ } }, "node_modules/vite": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", - "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -4244,6 +4518,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -4398,14 +4678,14 @@ } }, "@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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" } }, "@babel/compat-data": { @@ -4419,6 +4699,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -4516,15 +4797,15 @@ "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "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==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true }, "@babel/helper-validator-option": { @@ -4534,22 +4815,22 @@ "dev": true }, "@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "requires": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" } }, "@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==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "requires": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.29.0" } }, "@babel/plugin-transform-react-jsx-self": { @@ -4571,14 +4852,14 @@ } }, "@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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { @@ -4597,168 +4878,23 @@ } }, "@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, - "@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==", + "@emnapi/wasi-threads": { + "version": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, - "optional": true + "optional": true, + "requires": { + "tslib": "^2.4.0" + } }, "@eslint-community/eslint-utils": { "version": "4.4.1", @@ -4879,6 +5015,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "dev": true, + "optional": true, + "requires": { + "@tybys/wasm-util": "^0.10.1" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4905,10 +5051,140 @@ "fastq": "^1.6.0" } }, + "@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true + }, "@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==" + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==" + }, + "@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "dev": true, + "optional": true + }, + "@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "dev": true, + "optional": true, + "requires": { + "@napi-rs/wasm-runtime": "^1.1.1" + } + }, + "@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "dev": true, + "optional": true + }, + "@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true + }, + "@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } }, "@types/babel__core": { "version": "7.20.5", @@ -4962,6 +5238,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "dev": true, + "peer": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -4997,7 +5274,8 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -5007,9 +5285,9 @@ "requires": {} }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5130,9 +5408,9 @@ "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==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -5144,6 +5422,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5288,6 +5567,12 @@ "object-keys": "^1.1.1" } }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5450,36 +5735,6 @@ "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", @@ -5491,6 +5746,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5798,6 +6054,13 @@ "reusify": "^1.0.4" } }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5818,9 +6081,9 @@ } }, "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, "for-each": { @@ -5839,9 +6102,9 @@ "dev": true }, "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, "optional": true }, @@ -6300,9 +6563,9 @@ "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==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -6352,6 +6615,103 @@ "type-check": "~0.4.0" } }, + "lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "requires": { + "detect-libc": "^2.0.3", + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "dev": true, + "optional": true + }, + "lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "dev": true, + "optional": true + }, + "lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "dev": true, + "optional": true + }, + "lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "dev": true, + "optional": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6373,9 +6733,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -6388,9 +6748,9 @@ "dev": true }, "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, "natural-compare": { @@ -6544,6 +6904,13 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "peer": 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", @@ -6551,12 +6918,12 @@ "dev": true }, "postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "requires": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } @@ -6593,6 +6960,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -6601,6 +6969,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6618,20 +6987,20 @@ "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==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", "requires": { - "@remix-run/router": "1.22.0" + "@remix-run/router": "1.23.2" } }, "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==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "requires": { - "@remix-run/router": "1.22.0", - "react-router": "6.29.0" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" } }, "reflect.getprototypeof": { @@ -6685,13 +7054,29 @@ "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==", + "rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "dev": true, "requires": { - "fsevents": "~2.3.2" + "@oxc-project/types": "=0.122.0", + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12", + "@rolldown/pluginutils": "1.0.0-rc.12" } }, "run-parallel": { @@ -6969,6 +7354,23 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7069,15 +7471,18 @@ } }, "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" + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "dev": true, + "peer": true, + "requires": { + "fsevents": "~2.3.3", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" } }, "which": { diff --git a/package.json b/package.json index 0caab10..fc11f16 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": "^8.0.3" }, "babel": { "presets": [ @@ -55,8 +55,8 @@ }, "dependencies": { "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/src/ParentDashboard/components/CenterPanel.jsx b/src/ParentDashboard/components/CenterPanel.jsx new file mode 100644 index 0000000..ae472d2 --- /dev/null +++ b/src/ParentDashboard/components/CenterPanel.jsx @@ -0,0 +1,123 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import "../style ParentDash/styleCePanel.css"; + + +const CenterPanel = ({ childName, pendingTasksCount }) => { + const [activeTab, setActiveTab] = useState('Tareas'); + // Estado para controlar el filtro interno (sub-pestañas) + const [subFilter, setSubFilter] = useState('principal'); // 'principal' o 'secundario' + + // Resetear el sub-filtro al cambiar de pestaña principal + const handleTabChange = (tab) => { + setActiveTab(tab); + setSubFilter('principal'); // Por defecto, al cambiar de pestaña, vuelve a la primera opción + }; + + const getActionButtonText = () => { + switch (activeTab) { + case 'Tareas': return 'Nueva Tarea'; + case 'Cupones': return 'Nuevo Cupón'; + case 'Gran Premio': return 'Crear Gran Premio'; + default: return '+ Nuevo'; + } + }; + + // Renderizado dinámico de los botones de sub-filtro + const renderSubFilters = () => { + let labels = { first: '', second: '' }; + + if (activeTab === 'Tareas') { + labels = { first: 'Por hacer', second: 'Aprobadas' }; + } else if (activeTab === 'Cupones' || activeTab === 'Gran Premio') { + labels = { first: 'Sin canjear', second: 'Canjeado' }; + } + + return ( +
+
+ + +
+
+ ); + }; + + return ( +
+
+

Misiones de {childName}

+
+ +
+
+

Tareas pendientes por aprobar: {pendingTasksCount}

+
+
+ +
+

Gestión de Misiones

+
+
+
+ {/* 4. Agregamos onClick para cambiar la pestaña y una clase 'active' para CSS */} + + + +
+ +
+ +
+
+ + {/* Renderizamos los nuevos botones debajo de las pestañas principales */} +
+ {renderSubFilters()} +
+ +
+

+ Mostrando: {activeTab} + {subFilter === 'principal' ? ' (Pendientes/Sin canjear)' : ' (Aprobadas/Canjeados)'} +

+
+
+
+
+ ); +}; + +CenterPanel.propTypes = { + childName: PropTypes.string.isRequired, + pendingTasksCount: PropTypes.number.isRequired +}; + +export default CenterPanel; \ No newline at end of file diff --git a/src/ParentDashboard/components/LeftPanel.jsx b/src/ParentDashboard/components/LeftPanel.jsx new file mode 100644 index 0000000..69653d8 --- /dev/null +++ b/src/ParentDashboard/components/LeftPanel.jsx @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { ChildWizard } from "../../front/components/ChildProfileCreation/ChildWizard"; +import "../style ParentDash/styleLeftPanel.css"; + +const LeftPanel = ({ parentName, childrenProfiles }) => { + const [showWizard, setShowWizard] = useState(false); + const [selectedId, setSelectedId] = useState(null); + + return ( + + ); +}; + +LeftPanel.propTypes = { + parentName: PropTypes.string.isRequired, + childrenProfiles: PropTypes.array.isRequired +}; + +export default LeftPanel; diff --git a/src/ParentDashboard/components/RightPanel.jsx b/src/ParentDashboard/components/RightPanel.jsx new file mode 100644 index 0000000..ae44d11 --- /dev/null +++ b/src/ParentDashboard/components/RightPanel.jsx @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import "../style ParentDash/styleRightPanel.css"; + +const RightPanel = ({ grandPrizeName, grandPrizeImage, tasks = [] }) => { + // 1. ESTADO: Controla qué mes y año estamos visualizando + const [viewDate, setViewDate] = useState(new Date()); + const [selectedTasks, setSelectedTasks] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + + // 2. CÁLCULOS DINÁMICOS basándonos en viewDate + const year = viewDate.getFullYear(); + const month = viewDate.getMonth(); // 0 (Enero) a 11 (Diciembre) + + // Formatear el encabezado (ej: "abril de 2026") + const currentMonthLabel = new Intl.DateTimeFormat('es-ES', { + month: 'long', + year: 'numeric' + }).format(viewDate); + + // Obtener cuántos días tiene el mes actual de la vista + const daysInMonth = new Date(year, month + 1, 0).getDate(); + const daysArray = Array.from({ length: daysInMonth }, (_, i) => i + 1); + + // 3. FUNCIONES DE NAVEGACIÓN + const changeMonth = (offset) => { + // offset puede ser -1 (atrás) o 1 (adelante) + const newDate = new Date(year, month + offset, 1); + setViewDate(newDate); + }; + + const handleDayClick = (day) => { + // Formato YYYY-MM-DD para comparar con las tareas + const dateStr = `${year}-${(month + 1).toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; + const dayTasks = tasks.filter(t => t.date === dateStr); + + setSelectedTasks(dayTasks); + setIsModalOpen(true); + }; + + return ( + + ); +}; + +RightPanel.propTypes = { + grandPrizeName: PropTypes.string, + grandPrizeImage: PropTypes.string, + tasks: PropTypes.array +}; + +export default RightPanel; \ No newline at end of file diff --git a/src/ParentDashboard/pages/ParentAdmin.jsx b/src/ParentDashboard/pages/ParentAdmin.jsx new file mode 100644 index 0000000..a9f097d --- /dev/null +++ b/src/ParentDashboard/pages/ParentAdmin.jsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import LeftPanel from '../components/LeftPanel'; +import CenterPanel from '../components/CenterPanel'; +import RightPanel from '../components/RightPanel'; +import "../style ParentDash/stylePAdmin.css"; + +export const ParentAdmin = () => { + // 1. Datos de los hijos + const misHijos = [ + { id: 1, name: "Hijo 1", lastConnection: new Date() }, // Si usas fechas + { id: 2, name: "Hijo 2", lastConnection: new Date() } +]; + + + const [tasks, setTasks] = useState([ + { id: 101, title: "Lavar platos", date: "2026-04-06", points: 10 }, + { id: 102, title: "Hacer la cama", date: "2026-04-06", points: 5 }, + { id: 103, title: "Estudiar", date: "2026-04-07", points: 20 }, + ]); + + return ( +
+
+ + {/* 1. Left Panel (Identidad y Selección) */} + + + {/* 2. Center Panel (Gestión de Tareas) */} +
+
+ +
+
+ + {/* 3. Right Panel (Premios y Calendario con Tareas) */} +
+
+ +
+
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/ParentDashboard/style ParentDash/styleCePanel.css b/src/ParentDashboard/style ParentDash/styleCePanel.css new file mode 100644 index 0000000..259b6da --- /dev/null +++ b/src/ParentDashboard/style ParentDash/styleCePanel.css @@ -0,0 +1,77 @@ +.missions-btn { + display: flex; + justify-content: space-between; +} + +.tit_mission { + color: #3dc9b6; +} + +h4 { + margin: 0; + padding: 4px 0px; +} + +.left_btn button { + background: none; + border: none; + padding: 4px 16px 4px 0px; + transition: all 0.3s ease; + cursor: pointer; + font-weight: 600; + font-size: 16px; +} + +.left_btn button.active { + color:#3dc9b6; + cursor: pointer; + +} + +.filter-container { + padding: 4px 0px; +} +/* Contenedor principal de los botones de filtro */ +.sub-filters-wrapper { + display: inline-flex; + background-color: white; + border-radius: 5dvh; /* Hace el borde totalmente redondo */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); /* Sombra suave */ + border: 1px solid #f0f0f0; +} + +/* Estilo base de los botones */ +.sub-filter-btn { + border: none; + background: none; + padding: 2px 16px; + border-radius: 50px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: all 0.3s ease; + color: #4dbfb6; /* Color verde del texto cuando no está activo */ +} + +/* Estilo cuando el botón está ACTIVO */ +.sub-filter-btn.active { + background-color: #3dc9b6; /* El color verde de tu imagen */ + color: white; /* Texto blanco para resaltar */ +} + +/* Estilo cuando pasas el mouse (opcional) */ +.sub-filter-btn:hover:not(.active) { + background-color: #f9f9f9; +} + +.action-btn { + border: none; + background: #4dbfb6; + padding: 6px 16px; + border-radius: 50px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: all 0.3s ease; + color: white; +} diff --git a/src/ParentDashboard/style ParentDash/styleLeftPanel.css b/src/ParentDashboard/style ParentDash/styleLeftPanel.css new file mode 100644 index 0000000..3e7d827 --- /dev/null +++ b/src/ParentDashboard/style ParentDash/styleLeftPanel.css @@ -0,0 +1,175 @@ +/* --- 1. ESTRUCTURA BASE Y MÓVIL (Mobile First) --- */ + +.left-panel { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.panel-header h2 { + margin: 0; + font-size: 1.6rem; + color: #1e293b; + letter-spacing: -0.025em; +} + +.panel-content { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 15px; + width: 100%; +} + +/* --- BOTONES (Estilos base comunes) --- */ +button.btn-create-child-profile, +.child-profile { + cursor: pointer; + white-space: nowrap; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid transparent; /* Evita saltos al activarse el borde */ + background: none; +} + +/* Botón Crear Perfil */ +button.btn-create-child-profile { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + padding: 15px 20px; /* Ajustado para mejor balance */ + border-radius: 12px; + font-weight: bold; + color: #3dc9b6; + flex-shrink: 0; +} + +button.btn-create-child-profile:hover, +.child-profile:hover { + background-color: rgba(61, 201, 182, 0.1); /* Hover sutil */ + transform: translateY(-2px); +} + +/* ESTADO SELECCIONADO (Crear Perfil) */ +button.btn-create-child-profile.selected { + background-color: #f0fdfa; + border: 2px solid #3dc9b6; + color: #3dc9b6; /* Corregido de morado a turquesa */ + box-shadow: 0 4px 10px rgba(72, 201, 176, 0.2); +} + +/* Efecto de clic físico */ +.btn-create-child-profile:active, +.child-profile:active { + transform: scale(0.95); +} + +/* --- LISTA DE HIJOS --- */ +.children-list { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow-x: auto; + list-style: none; + padding: 0 0 10px 0; + margin: 0; + gap: 10px; + -webkit-overflow-scrolling: touch; + width: 100%; +} + +.child-profile { + display: flex; + flex-direction: column; + align-items: center; + padding: 15px; + border-radius: 15px; + color: #475569; + min-width: 120px; + min-height: 120px; +} + +/* ESTADO SELECCIONADO (Hijos) */ +.child-item.active .child-profile { + background-color: #f0fdfa; + border: 2px solid #3dc9b6; + transform: translateY(0); + box-shadow: 0 4px 10px rgba(72, 201, 176, 0.2); +} + +.child-item.active .child-name { + color: #3dc9b6; + font-weight: bold; +} + +.child-avatar { + width: 60px; /* Tamaño controlado */ + height: 60px; + object-fit: contain; + margin-bottom: 8px; +} + +.child-name { + font-weight: bold; + font-size: 1rem; + color: #48c9b0; +} + +/* --- 2. MEDIA QUERY: ESCRITORIO --- */ +@media (min-width: 1024px) { + .panel-content { + flex-direction: column; + align-items: stretch; + } + + button.btn-create-child-profile { + flex-direction: column; + width: 100%; + min-height: 100px; + padding: 20px 10px; + } + + .plus-icon-container { + background-color: rgba(61, 201, 182, 0.1); + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 8px; + } + + /* El icono también cambia si está seleccionado */ + button.btn-create-child-profile.selected .plus-icon-container { + background-color: #3dc9b6; + color: white; + } + + .children-list { + flex-direction: column; + overflow-x: visible; + gap: 15px; + } + + .child-item { + width: 100%; + } + + .child-profile { + flex-direction: column; + justify-content: flex-start; + gap: 15px; + width: 100%; + min-width: unset; + min-height: auto; + padding: 10px 15px; + } + + .child-avatar { + width: 40px; + height: 40px; + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/src/ParentDashboard/style ParentDash/stylePAdmin.css b/src/ParentDashboard/style ParentDash/stylePAdmin.css new file mode 100644 index 0000000..788363a --- /dev/null +++ b/src/ParentDashboard/style ParentDash/stylePAdmin.css @@ -0,0 +1,94 @@ +/* Contenedor principal de fondo */ +.dashboard-wrapper { + min-height: 100vh; + padding: 2rem; + background-color: #ffffff; + background-image: + radial-gradient(at 0% 0%, #f2fcff 0%, transparent 50%), + radial-gradient(at 100% 0%, #e9f1f6 0%, transparent 50%), + radial-gradient(at 100% 100%, #f2fcff 0%, transparent 50%), + radial-gradient(at 0% 100%, #e9f1f6 0%, transparent 50%), + radial-gradient(at 50% 50%, #ffffff 0%, transparent 50%); + background-attachment: fixed; +} + +/* El contenedor de las 3 columnas */ +.dashboard-content { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: stretch; + gap: 24px; + max-width: 1400px; + margin: 0 auto; + min-height: calc(100vh - 4rem); + /* background-color: #eef9f7; <- Opcional, si quieres ver el fondo del contenedor */ +} + +/* Configuración de Paneles (Escritorio) */ +.panel-left { + flex: 0 0 15%; /* Ocupa exactamente el 25% */ + display: flex; + flex-direction: column; +} + +.panel-right { + flex: 0 0 30%; /* Ocupa exactamente el 25% */ + display: flex; + flex-direction: column; +} + +.panel-center { + flex: 1; /* Toma el espacio restante (aprox 50%) */ + display: flex; + flex-direction: column; + /* Eliminamos padding lateral aquí para que la card controle el espaciado interno */ +} + +/* Las cajas blancas redondeadas (Cards) */ +.card-container { + background-color: #ffffff; + padding: 1.5rem; + border-radius: 24px; /* Mantengo tus 24px que se ven más modernos */ + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03); + flex-grow: 1; + height: 100%; +} + +/* --- Responsive --- */ + +/* Tablets y Laptops pequeñas */ +@media (max-width: 1024px) { + .dashboard-wrapper { + padding: 1rem; + } + + .dashboard-content { + flex-direction: column; /* Apila uno debajo de otro */ + gap: 16px; + min-height: auto; + } + + .panel-left, + .panel-center, + .panel-right { + width: 100%; + flex: none; + } + + /* ELIMINADO EL ORDER: -1 */ + /* Ahora el orden será: + 1. Papá Pérez (Left) + 2. Misiones (Center) + 3. Premios (Right) + */ +} + +/* Ajustes finos para móviles muy pequeños */ +@media (max-width: 480px) { + .card-container { + padding: 1rem; + border-radius: 16px; + } +} + diff --git a/src/ParentDashboard/style ParentDash/styleRightPanel.css b/src/ParentDashboard/style ParentDash/styleRightPanel.css new file mode 100644 index 0000000..82268c5 --- /dev/null +++ b/src/ParentDashboard/style ParentDash/styleRightPanel.css @@ -0,0 +1,111 @@ +/* Sección Gran Premio */ +aside{ + font-family: Arial, Helvetica, sans-serif; +} +.prize-card { + text-align: center; + background: #f8fafc; + padding: 1rem; + border-radius: 15px; + margin-bottom: 1rem; +} +.prize-img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; } +.prize-name { font-weight: bold; margin-top: 5px; color: #1e293b; } + +/* Calendario */ +.calendar-header h4 { + text-transform: capitalize; + + color: #475569; +} + +h4.calendar-title { + width: auto; /* El ancho lo define el texto */ + min-width: 140px; /* Opcional: evita saltos bruscos entre meses cortos y largos */ + text-align: center; + margin: 0; + font-size: 1.1rem; + font-weight: bold; + color: #1e293b; + text-transform: capitalize; /* "enero de 2026" -> "Enero de 2026" */ + white-space: nowrap; /* Evita que el año baje a la siguiente línea */ +} +.calendar-nav { + display: flex; + flex-direction: row; /* Todo en una fila */ + align-items: center; /* Centrado vertical perfecto */ + justify-content: center; /* Centra el conjunto en el panel */ + gap: 15px; /* Espacio constante entre flechas y texto */ + width: 100%; + margin-bottom: 20px; +} +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); /* 7 columnas */ + gap: 4px; +} + +.calendar-weekday { + font-size: 0.7rem; + text-align: center; + font-weight: bold; + color: #94a3b8; + padding-bottom: 5px; +} + +button.nav-btn{ +background: none; +border:none; + +} +.calendar-day { + aspect-ratio: 1/1; + background: #fff; + border: 1px solid #f1f5f9; + border-radius: 8px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.8rem; + position: relative; +} + +.calendar-day:hover { background: #eef2ff; } + +/* Puntos de tareas */ +.dots-container { + display: flex; + gap: 2px; + margin-top: 2px; + height: 4px; +} +.task-dot { + width: 4px; + height: 4px; + background-color: #6366f1; + border-radius: 50%; +} + + + +/* Modal */ +.modal-overlay { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0,0,0,0.4); + display: flex; align-items: center; justify-content: center; + z-index: 999; +} +.modal-content { + background: white; + padding: 2rem; + border-radius: 20px; + width: 300px; + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1); +} +.modal-task-list { list-style: none; padding: 0; margin: 1rem 0; } +.modal-task-list li { margin-bottom: 8px; font-size: 0.9rem; } +.pts { color: #10b981; font-weight: bold; font-size: 0.8rem; } +.btn-close { width: 100%; padding: 8px; border-radius: 8px; border: none; background: #f1f5f9; cursor: pointer; } \ No newline at end of file diff --git a/src/api/models.py b/src/api/models.py index a85fa14..15447b1 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -93,3 +93,109 @@ def serialize(self): "created_at": self.created_at.isoformat(), "product": self.product.serialize() } + +# ========================================== +# 🆕 MODELOS FINQUEST (SERIALIZADOS) +# ========================================== + + +class Child(db.Model): + __tablename__ = "child" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=False) + age: Mapped[int] = mapped_column(Integer, nullable=False) + pin: Mapped[str] = mapped_column(String(4), nullable=False) + avatar: Mapped[str] = mapped_column(String(255), nullable=True) + total_coins: Mapped[int] = mapped_column(Integer, default=0) + parent_id: Mapped[int] = mapped_column( + ForeignKey("user.id"), nullable=False) + + tasks: Mapped[list["Task"]] = relationship( + back_populates="child", cascade="all, delete-orphan") + small_goals: Mapped[list["SmallGoal"]] = relationship( + back_populates="child", cascade="all, delete-orphan") + grand_prize: Mapped["GrandPrize"] = relationship( + back_populates="child", cascade="all, delete-orphan") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "age": self.age, + "avatar": self.avatar, + "total_coins": self.total_coins, + "parent_id": self.parent_id + } + + +class Task(db.Model): + __tablename__ = "task" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=False) + coins: Mapped[int] = mapped_column(Integer, nullable=False) + days: Mapped[str] = mapped_column( + String(100), nullable=False) # Guardado como "L,M,X" + child_id: Mapped[int] = mapped_column( + ForeignKey("child.id"), nullable=False) + child: Mapped["Child"] = relationship(back_populates="tasks") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "coins": self.coins, + "days": self.days.split(",") if self.days else [], + "child_id": self.child_id + } + + +class SmallGoal(db.Model): + __tablename__ = "small_goal" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=False) + coins: Mapped[int] = mapped_column(Integer, nullable=False) + child_id: Mapped[int] = mapped_column( + ForeignKey("child.id"), nullable=False) + child: Mapped["Child"] = relationship(back_populates="small_goals") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "coins": self.coins, + "child_id": self.child_id + } + + +class GrandPrize(db.Model): + __tablename__ = "grand_prize" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=False) + coins: Mapped[int] = mapped_column(Integer, nullable=False) + image_url: Mapped[str] = mapped_column(String(255), nullable=True) + child_id: Mapped[int] = mapped_column( + ForeignKey("child.id"), nullable=False) + child: Mapped["Child"] = relationship(back_populates="grand_prize") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "coins": self.coins, + "image_url": self.image_url, + "child_id": self.child_id + } + + +class Reward(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + cost: Mapped[int] = mapped_column(Integer, nullable=False) + child_id: Mapped[int] = mapped_column( + ForeignKey("child.id"), nullable=False) + + def serialize(self): + return { + "cost": self.cost, + "child_id": self.child_id + + } diff --git a/src/api/routes.py b/src/api/routes.py index 7bef881..97d358e 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,16 +1,17 @@ """ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ +from datetime import datetime from flask import Blueprint, jsonify, request from flask_jwt_extended import ( create_access_token, get_jwt_identity, jwt_required ) - -from api.models import Order, Product, User, db from api.utils import APIException +from api.models import Child, Order, Product, Reward, Task, User, db, SmallGoal, GrandPrize + api = Blueprint("api", __name__) @@ -180,3 +181,179 @@ def create_order(): "message": "Order created successfully", "order": order.serialize() }), 201 + + +@api.route("/child-dashboard/", methods=["GET"]) +def get_child_dashboard(child_id): + child = db.session.get(Child, child_id) + if child is None: + raise APIException("Child not found", status_code=404) + + today = datetime.utcnow().date() + + if child.last_login_date is None: + child.streak = 1 + else: + last = child.last_login_date.date() + if last == today: + pass # ya entró hoy, no cambia nada + elif (today - last).days == 1: + child.streak += 1 # entró ayer, suma racha + else: + child.streak = 1 # rompió la racha, reinicia + + child.last_login_date = datetime.utcnow() + db.session.commit() + + tasks = Task.query.filter_by(child_id=child_id).all() + + tasks = Task.query.filter_by(child_id=child_id).all() + rewards = Reward.query.filter_by(child_id=child_id).all() + + return jsonify({ + "child": child.serialize(), + "tasks": [task.serialize() for task in tasks], + "rewards": [reward.serialize() for reward in rewards] + }), 200 + + +@api.route("/tasks//complete", methods=["PATCH"]) +def complete_task(task_id): + task = db.session.get(Task, task_id) + if task is None: + raise APIException("Task not found", status_code=404) + + if task.status == "completed": + raise APIException("Task already completed", status_code=400) + + task.status = "completed" + db.session.commit() + + return jsonify({ + "message": "Task marked as completed", + "task": task.serialize() + }), 200 + + +@api.route("/rewards//redeem", methods=["POST"]) +def redeem_reward(reward_id): + reward = db.session.get(Reward, reward_id) + if reward is None: + raise APIException("Reward not found", status_code=404) + + child = db.session.get(Child, reward.child_id) + if child.coins < reward.cost: + raise APIException("Not enough coins", status_code=400) + + child.coins -= reward.cost + db.session.commit() + + return jsonify({ + "message": "Reward redeemed successfully", + "coins_remaining": child.coins, + "reward": reward.serialize() + }), 200 + + return jsonify(mock_child_dashboard), 200 +# ========================================== +# ENDPOINTS PARA GESTIÓN DE PERFILES INFANTILES +# ========================================== + +# NOTA: Se ha comentado @jwt_required temporalmente para pruebas sin Login completo. + + +@api.route("/child", methods=["POST"]) +def create_child(): + """Crea un perfil infantil vinculado al padre (ID 1 temporal por desarrollo)""" + data = request.get_json() + # TODO: Integrar con get_jwt_identity() cuando el login esté listo + current_user_id = 1 + + name = data.get("name") + age = data.get("age") + pin = data.get("pin") + + if not all([name, age, pin]): + return jsonify({"message": "Nombre, edad y PIN son obligatorios"}), 400 + + new_child = Child( + name=name, + age=age, + pin=pin, + avatar=data.get("avatar", "default_avatar.png"), + parent_id=current_user_id + ) + + db.session.add(new_child) + db.session.commit() + return jsonify({"message": "Perfil creado", "child": new_child.serialize()}), 201 + +@api.route("/child//tasks", methods=["POST"]) +def create_tasks(child_id): + """Asigna una lista de tareas recurrentes a un perfil específico""" + data = request.get_json() + if not isinstance(data, list): + return jsonify({"message": "Formato de lista requerido"}), 400 + + for item in data: + new_task = Task( + name=item.get("name"), + coins=max(0, int(item.get("coins", 0))), # Evita monedas negativas + days=item.get("days", ""), + child_id=child_id + ) + db.session.add(new_task) + + db.session.commit() + return jsonify({"message": f"{len(data)} tareas asignadas correctamente"}), 201 + +@api.route("/child//small-goals", methods=["POST"]) +# @jwt_required() <-- comentado hasta enlazar con la creacion de usuario +def create_small_goals(child_id): + """Asigna una lista de premios intermedios a un hijo""" + data = request.get_json() + + if not isinstance(data, list): + return jsonify({"message": "Se esperaba una lista de premios"}), 400 + + for goal_data in data: + new_goal = SmallGoal( + name=goal_data.get("name"), + coins=goal_data.get("coins"), + child_id=child_id + ) + db.session.add(new_goal) + + db.session.commit() + return jsonify({"message": "Premios pequeños guardados"}), 201 + + +@api.route("/child//grand-prize", methods=["POST"]) +# @jwt_required() <-- comentado hasta enlazar con la creacion de usuario +def create_grand_prize(child_id): + """Configura el premio final para un hijo""" + data = request.get_json() + + new_prize = GrandPrize( + name=data.get("name"), + coins=data.get("coins"), + image_url=data.get("image_url", ""), + child_id=child_id + ) + + db.session.add(new_prize) + db.session.commit() + return jsonify({"message": "¡Gran Premio configurado!"}), 201 + +@api.route("/child//tasks", methods=["GET"]) +# @jwt_required() <-- comentado hasta enlazar con la creacion de usuario +def get_child_tasks(child_id): + """Obtiene todas las tareas asignadas a un niño específico""" + + tasks = Task.query.filter_by(child_id=child_id).all() + + results = [task.serialize() for task in tasks] + + return jsonify(results), 200 + + diff --git a/src/api/utils.py b/src/api/utils.py index 9c18b4d..fa1cb13 100644 --- a/src/api/utils.py +++ b/src/api/utils.py @@ -38,4 +38,4 @@ def generate_sitemap(app):

API HOST:

Start working on your project by following the Quick Start

Remember to specify a real endpoint path like:

-
    """+links_html+"
" +
    """+links_html+"
" \ No newline at end of file diff --git a/src/app.py b/src/app.py index bffcaf4..c17b8e7 100644 --- a/src/app.py +++ b/src/app.py @@ -13,14 +13,18 @@ from api.routes import api from api.utils import APIException, generate_sitemap - 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 -# database condiguration +# Habilitamos CORS de forma total para evitar bloqueos en el entorno de desarrollo +# Esto permite que el puerto 3000 hable con el 3001 sin restricciones +CORS(app, resources={r"/api/*": {"origins": "*"}}) + +# database configuration db_url = os.getenv("DATABASE_URL") if db_url is not None: app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace( @@ -35,8 +39,6 @@ ) app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=8) -CORS(app, resources={r"/api/*": {"origins": "*"}}) - MIGRATE = Migrate(app, db, compare_type=True) db.init_app(app) @@ -68,6 +70,8 @@ def sitemap(): 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)): @@ -77,7 +81,6 @@ def serve_any_other_file(path): 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) diff --git a/src/front/Login.css b/src/front/Login.css new file mode 100644 index 0000000..b1e4a92 --- /dev/null +++ b/src/front/Login.css @@ -0,0 +1,133 @@ +/* Contenedor principal con fondo */ +.login-container { + min-height: 100vh; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 5%; + + background-image: url("./assets/img/fondo.jpg"); + background-size: cover; + background-position: center; +} + +/* Card con su propio fondo */ +.login-card { + width: 400px; + height: 500px; + min-height: 500px; + padding: 2rem; + border-radius: 20px; + + background-image: url("./assets/img/fondo_login.png"); + background-size: cover; + background-position: center; + + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + + display: flex; + flex-direction: column; + justify-content: center; +} + +/* Título */ +.login-title { + text-align: center; + margin-bottom: 1.5rem; + color: #5a3e2b; +} + +/* Formulario */ +.login-form { + display: flex; + flex-direction: column; +} + +/* Inputs */ +.login-input { + margin-bottom: 1rem; + padding: 0.8rem; + border-radius: 10px; + border: none; + background: rgba(255, 255, 255, 0.85); + font-size: 1rem; +} + +/* Botón */ +.login-button { + padding: 0.8rem; + border-radius: 12px; + border: none; + background: #3db7a4; + color: white; + font-weight: bold; + cursor: pointer; + transition: 0.3s; +} + +.login-button:hover { + background: #2ea393; +} + +/* Error */ +.login-error { + color: red; + font-size: 0.9rem; + margin-top: 0.5rem; +} + +/* Registro */ +.login-signup { + margin-top: 1rem; + text-align: center; +} + +.login-signup a { + color: #3db7a4; + font-weight: bold; + text-decoration: none; +} + +/* Imagen derecha */ +.login-image img { + max-width: 500px; +} + +/* ========================= */ +/* 📱 RESPONSIVE - TABLET */ +/* ========================= */ +@media (max-width: 1024px) { + .login-image img { + max-width: 350px; + } +} + +/* ========================= */ +/* 📱 RESPONSIVE - MOBILE */ +/* ========================= */ +@media (max-width: 768px) { + .login-container { + justify-content: center; + padding: 1rem; + } + + .login-image { + display: none; + } + + .login-card { + width: 100%; + max-width: 350px; + height: auto; + padding: 1.5rem; + } + + .login-input { + padding: 1rem; + font-size: 1rem; + } + + .login-button { + padding: 1rem; + } +} \ No newline at end of file diff --git a/src/front/ProfilesPage.css b/src/front/ProfilesPage.css new file mode 100644 index 0000000..567e846 --- /dev/null +++ b/src/front/ProfilesPage.css @@ -0,0 +1,121 @@ +/* Contenedor principal */ +.profiles-container { + min-height: 100vh; + background-color: #3db7a4; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + padding: 2rem; +} + +/* Grid de perfiles */ +.profiles-grid { + display: flex; + gap: 2rem; + margin-top: 2rem; + flex-wrap: wrap; + justify-content: center; +} + +/* Tarjeta de perfil */ +.profile-card { + cursor: pointer; + text-align: center; + transition: transform 0.3s, box-shadow 0.3s; + border-radius: 20px; + padding: 1rem; + background: rgba(255, 255, 255, 0.05); /* sutil */ +} +.profile-card:hover { + transform: scale(1.05); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); +} + +/* Avatar */ +.profile-card img { + width: 120px; + height: 120px; + border-radius: 50%; + object-fit: cover; + border: 2px solid #3db7a4; +} + +/* Nombre del perfil */ +.profile-card p { + margin-top: 0.5rem; + font-weight: bold; + color: white; + font-size: 1rem; +} + +/* Modal overlay */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.4); /* más claro que antes */ + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +} + +/* Modal box centrado con bordes redondeados */ +.modal-content { + background: #f9f9f9; + padding: 2rem; + border-radius: 20px; + text-align: center; + min-width: 300px; + max-width: 400px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); +} + +/* Input PIN */ +.modal-content input { + width: 100%; + padding: 0.8rem; + border-radius: 10px; + border: none; + background: rgba(0,0,0,0.05); + font-size: 1rem; + margin-top: 1rem; +} + +/* Botones estilo login */ +.modal-content button { + padding: 0.8rem; + border-radius: 12px; + border: none; + background: #3db7a4; + color: white; + font-weight: bold; + cursor: pointer; + transition: 0.3s; + margin-top: 1rem; +} +.modal-content button:hover { + background: #2ea393; +} +.modal-content button + button { + margin-left: 1rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .profiles-grid { + gap: 1rem; + } + .profile-card img { + width: 100px; + height: 100px; + } + .modal-content { + width: 90%; + padding: 1.5rem; + } +} \ No newline at end of file diff --git a/src/front/SignUp.css b/src/front/SignUp.css new file mode 100644 index 0000000..0ae451a --- /dev/null +++ b/src/front/SignUp.css @@ -0,0 +1,94 @@ +/* SignUp.css */ + +/* Contenedor principal */ +.auth-section { + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #0f172a; /* fondo oscuro consistente */ + color: white; + padding: 2rem; +} + +/* Card */ +.panel-card.auth-card { + width: 100%; + max-width: 400px; + background-size: cover; + background-position: center; + padding: 2rem; + border-radius: 20px; + box-shadow: 0 10px 30px rgba(0,0,0,0.15); + display: flex; + flex-direction: column; + justify-content: center; +} + +/* Título */ +.section-title { + text-align: center; + margin-bottom: 1.5rem; + color: #5a3e2b; +} + +/* Inputs */ +.auth-form .form-control { + margin-bottom: 1rem; + padding: 0.8rem; + border-radius: 10px; + border: none; + background: rgba(255,255,255,0.85); + font-size: 1rem; +} + +/* Botón */ +.btn-primary-soft { + padding: 0.8rem; + border-radius: 12px; + border: none; + background: #3db7a4; + color: white; + font-weight: bold; + cursor: pointer; + transition: 0.3s; +} + +.btn-primary-soft:hover { + background: #2ea393; +} + +/* Footnote */ +.auth-footnote { + margin-top: 1rem; + text-align: center; +} + +.auth-footnote a { + color: #3db7a4; + font-weight: bold; + text-decoration: none; +} + +/* Alertas de error */ +.alert.alert-danger { + margin-top: 0.5rem; + font-size: 0.9rem; +} + +/* ========================= */ +/* 📱 RESPONSIVE */ +@media (max-width: 768px) { + .panel-card.auth-card { + padding: 1.5rem; + } + + .auth-form .form-control { + padding: 1rem; + font-size: 1rem; + } + + .btn-primary-soft { + padding: 1rem; + } +} \ No newline at end of file diff --git a/src/front/assets/img/Cashtor.jpg b/src/front/assets/img/Cashtor.jpg new file mode 100644 index 0000000..5e5d643 Binary files /dev/null and b/src/front/assets/img/Cashtor.jpg differ diff --git a/src/front/assets/img/Castor-1.png b/src/front/assets/img/Castor-1.png new file mode 100644 index 0000000..6c8a146 Binary files /dev/null and b/src/front/assets/img/Castor-1.png differ diff --git a/src/front/assets/img/Castor-2.png b/src/front/assets/img/Castor-2.png new file mode 100644 index 0000000..330f2d7 Binary files /dev/null and b/src/front/assets/img/Castor-2.png differ diff --git a/src/front/assets/img/Castor-2b.png b/src/front/assets/img/Castor-2b.png new file mode 100644 index 0000000..dba2ae3 Binary files /dev/null and b/src/front/assets/img/Castor-2b.png differ diff --git a/src/front/assets/img/ChatGPT_Image_18_mar_2026_19_14_24.png b/src/front/assets/img/ChatGPT_Image_18_mar_2026_19_14_24.png new file mode 100644 index 0000000..b04f9ce Binary files /dev/null and b/src/front/assets/img/ChatGPT_Image_18_mar_2026_19_14_24.png differ diff --git a/src/front/assets/img/cashtor_coins.png b/src/front/assets/img/cashtor_coins.png new file mode 100644 index 0000000..01201b1 Binary files /dev/null and b/src/front/assets/img/cashtor_coins.png differ diff --git a/src/front/assets/img/coin.png b/src/front/assets/img/coin.png new file mode 100644 index 0000000..ec34f57 Binary files /dev/null and b/src/front/assets/img/coin.png differ diff --git a/src/front/assets/img/fondo.jpg b/src/front/assets/img/fondo.jpg new file mode 100644 index 0000000..5cdd684 Binary files /dev/null and b/src/front/assets/img/fondo.jpg differ diff --git a/src/front/assets/img/fondo_login.png b/src/front/assets/img/fondo_login.png new file mode 100644 index 0000000..982be65 Binary files /dev/null and b/src/front/assets/img/fondo_login.png differ diff --git a/src/front/assets/img/hucha.png b/src/front/assets/img/hucha.png new file mode 100644 index 0000000..3d1ed54 Binary files /dev/null and b/src/front/assets/img/hucha.png differ diff --git a/src/front/assets/img/logo.png b/src/front/assets/img/logo.png new file mode 100644 index 0000000..6cef7ef Binary files /dev/null and b/src/front/assets/img/logo.png differ diff --git a/src/front/assets/img/monedas.png b/src/front/assets/img/monedas.png new file mode 100644 index 0000000..7e178ba Binary files /dev/null and b/src/front/assets/img/monedas.png differ diff --git a/src/front/assets/img/monedas2.png b/src/front/assets/img/monedas2.png new file mode 100644 index 0000000..22da995 Binary files /dev/null and b/src/front/assets/img/monedas2.png differ diff --git a/src/front/assets/img/monedas3.png b/src/front/assets/img/monedas3.png new file mode 100644 index 0000000..bb4445b Binary files /dev/null and b/src/front/assets/img/monedas3.png differ diff --git a/src/front/assets/img/monedas4.png b/src/front/assets/img/monedas4.png new file mode 100644 index 0000000..7c31c46 Binary files /dev/null and b/src/front/assets/img/monedas4.png differ diff --git a/src/front/assets/img/perro.png b/src/front/assets/img/perro.png new file mode 100644 index 0000000..d1fc25c Binary files /dev/null and b/src/front/assets/img/perro.png differ diff --git a/src/front/components/ChildDashboard/DailyTaskList.jsx b/src/front/components/ChildDashboard/DailyTaskList.jsx new file mode 100644 index 0000000..34436cf --- /dev/null +++ b/src/front/components/ChildDashboard/DailyTaskList.jsx @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from "react"; + +export const DailyTaskList = ({ childId }) => { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + + // 1. Lógica del Traductor: Averiguamos qué letra toca hoy + const getTodayLetter = () => { + const days = ["D", "L", "M", "X", "J", "V", "S"]; + return days[new Date().getDay()]; + }; + + const today = getTodayLetter(); + + useEffect(() => { + const fetchTasks = async () => { + try { + const baseUrl = process.env.BACKEND_URL.replace(/\/$/, ""); + const response = await fetch(`${baseUrl}/api/child/${childId}/tasks`); + if (response.ok) { + const data = await response.json(); + + // 2. Lógica de Filtrado: Solo tareas que incluyan el día de hoy + // El backend nos devuelve 'days' como un array gracias al serialize que hicimos + const filtered = data.filter(task => task.days.includes(today)); + + setTasks(filtered); + } + } catch (error) { + console.error("Error cargando tareas:", error); + } finally { + setLoading(false); + } + }; + + if (childId) fetchTasks(); + }, [childId, today]); + + if (loading) return
; + + return ( +
+

+ Tareas para hoy ({today}) +

+ {tasks.length === 0 ? ( +

¡Día libre! No hay tareas para hoy.

+ ) : ( +
    + {tasks.map(task => ( +
  • + {task.name} + 🪙 {task.coins} +
  • + ))} +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildHeader.jsx b/src/front/components/ChildHeader.jsx new file mode 100644 index 0000000..d0385df --- /dev/null +++ b/src/front/components/ChildHeader.jsx @@ -0,0 +1,42 @@ +import React from "react"; + +export const ChildHeader = ({ child }) => { + return ( +
+
+
🦁
+
+

Perfil

+

{child.name}

+
+
+ +
+
+
+ + Nivel {child.level} 👑 + +
+
+
+
+
+ +
+
+ + 🪙 {child.coins} Monedas + +
+
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildGrandPrizeSet.jsx b/src/front/components/ChildProfileCreation/ChildGrandPrizeSet.jsx new file mode 100644 index 0000000..10c6da5 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildGrandPrizeSet.jsx @@ -0,0 +1,133 @@ +import React, { useState, useRef } from "react"; +import { ProgressBar } from "./ProgressBar"; +import cashtorImg from "../../assets/img/Cashtor.jpg"; // 🔴 Importamos el avatar +import "./ChildWizard.css"; + +export const ChildGrandPrizeSet = ({ onBack, onNextStep, step, formData }) => { + const [goalName, setGoalName] = useState(""); + const [goalAmount, setGoalAmount] = useState(5000); + const [preview, setPreview] = useState(null); + const fileInputRef = useRef(null); + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + if (file.size > 2 * 1024 * 1024) { + alert("¡Foto demasiado grande! Por favor, elige una de menos de 2MB."); + e.target.value = ""; + return; + } + setPreview(URL.createObjectURL(file)); + } + }; + + const handleFinalClick = () => { + onNextStep({ + name: goalName, + coins: parseInt(goalAmount) || 0, + image_url: "" + }); + }; + + return ( +
+ + {/* CABECERA */} +
+ {/* 🔴 SOLO EL AVATAR CORONANDO EL MODAL */} +
+ Avatar +
+ +

¡Gran Premio!

+
+ + {/* CUERPO CENTRAL */} +
+
+
+ setGoalName(e.target.value)} + /> +
+
+ setGoalAmount(e.target.value)} + /> + 🪙 +
+
+ +

+ 💡 20 monedas = 1€. Estimación de valor: {(goalAmount / 20).toFixed(2)}€ +

+ + + +
fileInputRef.current.click()} + style={{ + border: "3px dashed #32a89b", + borderRadius: "30px", + backgroundColor: preview ? "white" : "rgba(50, 168, 155, 0.05)", + cursor: "pointer", + overflow: "hidden", + minHeight: "220px", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + transition: "all 0.3s ease", + }} + > + {preview ? ( + Preview + ) : ( +
+ 📸 +

Subir foto del premio

+

Formato JPG o PNG (Max 2MB)

+
+ )} +
+
+ + {/* PIE FIJO */} +
+ +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildRegistration.jsx b/src/front/components/ChildProfileCreation/ChildRegistration.jsx new file mode 100644 index 0000000..6dbc9e6 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildRegistration.jsx @@ -0,0 +1,177 @@ +import React, { useState } from "react"; +import cashtorImg from "../../assets/img/Cashtor.jpg"; +import { ProgressBar } from "./ProgressBar"; +import "./ChildWizard.css"; + +export const ChildRegistration = ({ onClose, onNextStep, step }) => { + const [name, setName] = useState(""); + const [age, setAge] = useState(""); + const [pin, setPin] = useState(""); + const [selectedAvatar, setSelectedAvatar] = useState(1); // Cashtor Red por defecto + const [isSubmitting, setIsSubmitting] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + + const avatars = [ + { id: 1, img: cashtorImg, name: "Cashtor Red" }, + { id: 2, img: cashtorImg, name: "Cashtor Scuba" }, + { id: 3, img: cashtorImg, name: "Cashtor Pink" }, + { id: 4, img: cashtorImg, name: "Cashtor Flower" } + ]; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (pin.length !== 4) return; + setIsSubmitting(true); + // Simulamos un pequeño tiempo de carga para que el botón muestre "Cargando..." + await new Promise(resolve => setTimeout(resolve, 800)); + setIsSubmitting(false); + setIsSuccess(true); + }; + + const handleConfirmAndNext = () => { + const childData = { + name: name, + age: parseInt(age), + pin: pin, + avatar: `avatar_${selectedAvatar}.png` + }; + onNextStep({ child: childData }); + }; + + // Buscamos la imagen del avatar seleccionado para mostrarla en el resumen + const currentAvatar = avatars.find(av => av.id === selectedAvatar) || avatars[0]; + + return ( +
+ + {isSuccess ? ( + /* VISTA DE ÉXITO (Enfocada al padre/madre) */ + <> +
+

¡Perfil Creado!

+
+ +
+
+ Avatar seleccionado +
+

{name}

+

+ El espacio para tu hijo/a ya está listo.
¿Configuramos sus metas y tareas ahora? +

+
+ +
+ + +
+ + ) : ( + /* FORMULARIO DE REGISTRO */ +
+
+

Crear Perfil del niño/a

+
+ +
+ {/* NOMBRE */} +
+ + setName(e.target.value)} + required + /> +
+ + {/* EDAD Y PIN */} +
+
+ + setAge(e.target.value)} + required + /> +
+
+ + setPin(e.target.value.replace(/\D/g, ""))} + required + /> +
+
+ + {/* SELECCIÓN DE AVATAR */} +
+ +
+ {avatars.map((av) => ( + {av.name} setSelectedAvatar(av.id)} + style={{ + width: "75px", + height: "75px", + borderRadius: "50%", + cursor: "pointer", + objectFit: "cover", + border: selectedAvatar === av.id ? "4px solid #32a89b" : "2px solid transparent", + transition: "transform 0.2s ease", + transform: selectedAvatar === av.id ? "scale(1.1)" : "scale(1)" + }} + /> + ))} +
+
+
+ + {/* PIE DE PÁGINA FIJO */} +
+ +
+ + +
+
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildSmallGoals.jsx b/src/front/components/ChildProfileCreation/ChildSmallGoals.jsx new file mode 100644 index 0000000..e77fe0b --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildSmallGoals.jsx @@ -0,0 +1,133 @@ +import React, { useState } from "react"; +import { ProgressBar } from "./ProgressBar"; +import cashtorImg from "../../assets/img/Cashtor.jpg"; +import "./ChildWizard.css"; + +export const ChildSmallGoals = ({ onBack, onNextStep, step, formData }) => { + + const childName = formData?.child?.child?.name || formData?.child?.name || "Niño/a"; + + const [addedRewards, setAddedRewards] = useState([ + { id: 1, name: "30 min de videojuegos", coins: 50 }, + { id: 2, name: "Elegir la cena del viernes", coins: 100 }, + { id: 3, name: "Dormir 30 min más tarde", coins: 150 }, + { id: 4, name: "Ir al parque el finde", coins: 80 }, + ]); + + const [newRewardName, setNewRewardName] = useState(""); + const [newRewardCoins, setNewRewardCoins] = useState(50); + + const addNewReward = () => { + if (!newRewardName.trim()) return; + const newR = { + id: Date.now(), + name: newRewardName, + coins: parseInt(newRewardCoins) || 50 + }; + setAddedRewards([newR, ...addedRewards]); + setNewRewardName(""); + setNewRewardCoins(50); + }; + + const removeReward = (id) => { + setAddedRewards(addedRewards.filter(r => r.id !== id)); + }; + + const handleNext = () => { + const formattedRewards = addedRewards.map(r => ({ + name: r.name, + coins: r.coins + })); + onNextStep(formattedRewards); + }; + + return ( +
+ + {/* CABECERA */} +
+ + {/* 🔴 SOLO EL AVATAR CORONANDO EL MODAL */} +
+ Avatar +
+ + {/* Título justo debajo */} +

Crear Cupones

+ +
+ setNewRewardName(e.target.value)} + /> +
+ 🪙 + setNewRewardCoins(e.target.value)} + /> +
+ +
+
+ + {/* CUERPO CENTRAL */} +
+ + + {addedRewards.length === 0 && ( +

Añade cupones para que el niño pueda canjearlos

+ )} + + {addedRewards.map((r) => ( +
+ {r.name} +
+ 🪙 {r.coins} +
+ +
+ ))} +
+ + {/* PIE FIJO */} +
+

💡 Sugerencia: 20 🪙 = 1€

+ +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildSummary.jsx b/src/front/components/ChildProfileCreation/ChildSummary.jsx new file mode 100644 index 0000000..c2d7756 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildSummary.jsx @@ -0,0 +1,95 @@ +import React from "react"; +import cashtorImg from "../../assets/img/Cashtor.jpg"; // Ajusta la ruta si es necesario +import "./ChildWizard.css"; + +export const ChildSummary = ({ formData, isSaving, saveError, onClose }) => { + // Extraemos los datos para que el código sea más limpio abajo + const childName = formData.child?.child?.name || "Niño/a"; + const totalTasks = formData.tasks?.length || 0; + const totalCoupons = formData.smallGoals?.length || 0; + const grandPrizeName = formData.grandPrize?.name || "Sin premio"; + const grandPrizeCoins = formData.grandPrize?.coins || 0; + + return ( +
+ +
+

¡Misión Completada!

+

+ El perfil de {childName} está listo. +

+
+ +
+ + {/* Imagen central */} +
+ Éxito +
+ + {/* Resumen de configuración */} +
+

Resumen del Perfil

+ +
+ 📝 Tareas asignadas: + {totalTasks} +
+ +
+ 🎟️ Cupones disponibles: + {totalCoupons} +
+ +
+ 🏆 Gran Premio: +
+
{grandPrizeName}
+
🪙 {grandPrizeCoins}
+
+
+
+ + {/* Mensaje de estado (Guardando / Error) */} +
+ {isSaving && ⏳ Guardando configuración en la base de datos...} + {saveError && ❌ Error: {saveError}} + {!isSaving && !saveError && ✅ ¡Todo guardado correctamente!} +
+ +
+ +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildTaskSetting.jsx b/src/front/components/ChildProfileCreation/ChildTaskSetting.jsx new file mode 100644 index 0000000..6479906 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildTaskSetting.jsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; +import { ProgressBar } from "./ProgressBar"; +import cashtorImg from "../../assets/img/Cashtor.jpg"; // 🔴 Importamos la imagen del avatar +import "./ChildWizard.css"; + +export const ChildTaskSetting = ({ onBack, onNextStep, step }) => { + const [addedTasks, setAddedTasks] = useState([ + { id: 1, name: "Hacer los deberes", coins: 10, days: ["L", "M", "X", "J", "V"] }, + { id: 2, name: "Sacar al perro", coins: 22, days: ["L", "M", "X", "J", "V"] }, + { id: 3, name: "Poner la mesa", coins: 10, days: ["L", "M", "X", "J", "V"] } + ]); + + const [newTaskName, setNewTaskName] = useState(""); + const [newTaskCoins, setNewTaskCoins] = useState(10); + const allDays = ["L", "M", "X", "J", "V", "S", "D"]; + + const addNewTask = () => { + if (!newTaskName.trim()) return; + const newTask = { + id: Date.now(), + name: newTaskName, + coins: parseInt(newTaskCoins) || 10, + days: ["L", "M", "X", "J", "V"] + }; + setAddedTasks([newTask, ...addedTasks]); + setNewTaskName(""); + setNewTaskCoins(10); + }; + + const toggleDay = (id, day) => { + setAddedTasks(addedTasks.map(t => + t.id === id ? { + ...t, + days: t.days.includes(day) ? t.days.filter(d => d !== day) : [...t.days, day] + } : t + )); + }; + + const removeTask = (id) => { + setAddedTasks(addedTasks.filter(t => t.id !== id)); + }; + + return ( +
+ +
+ + {/* 🔴 SOLO EL AVATAR CORONANDO EL MODAL */} +
+ Avatar del niño +
+ + {/* Título justo debajo */} +

Crear tareas de casa

+ +
+ setNewTaskName(e.target.value)} + /> +
+ 🪙 + setNewTaskCoins(e.target.value)} + /> +
+ {/* 🔴 Botón ajustado con height: "55px" */} + +
+
+ +
+ + + {addedTasks.length === 0 && ( +

Usa el buscador de arriba para añadir misiones

+ )} + + {addedTasks.map((task) => ( +
+ {task.name} + +
+ {allDays.map(d => ( + toggleDay(task.id, d)} + className={`task-day-bubble ${task.days.includes(d) ? 'active' : ''}`} + > + {d} + + ))} +
+ +
+ 🪙 {task.coins} +
+ + +
+ ))} +
+ +
+

💡 Sugerencia: 20 🪙 = 1€

+ +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildWizard.css b/src/front/components/ChildProfileCreation/ChildWizard.css new file mode 100644 index 0000000..d120f14 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildWizard.css @@ -0,0 +1,148 @@ +/* ========================================= + ESTRUCTURA PRINCIPAL DEL MODAL + ========================================= */ + +/* 1. EL BÚNKER PRINCIPAL */ +.wizard-container { + width: 650px; /* 🔴 REDUCIDO: Antes 800px, ahora 650px */ + height: 85vh; /* Se adapta a tu pantalla */ + max-height: 680px; /* 🔴 REDUCIDO: Antes 750px, ahora 680px */ + background-color: #f0fdfa; + border-radius: 40px; + display: flex; + flex-direction: column; + overflow: hidden; /* Esto corta lo que se intente salir */ + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2); + margin: auto; +} + +/* 2. EL PASO (Cuadro rojo gigante) */ +.wizard-step-wrapper { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; /* 🔴 CRÍTICO 1: Le prohíbe crecer más que el búnker principal */ +} + +/* 3. LA CABECERA (Cuadro rojo superior) */ +.wizard-header { + flex-shrink: 0; + padding: 30px 45px 10px 45px; +} + +/* 4. EL CUERPO (Cuadro rojo del medio) */ +.wizard-body { + flex: 1; /* Ocupa todo el centro */ + min-height: 0; /* 🔴 CRÍTICO 2: Activa el scroll y salva al footer */ + overflow-y: auto; + padding: 10px 45px; + display: block; /* EL FIX: Volvemos a comportamiento normal, nada de flex aquí dentro */ +} + +/* 5. EL FOOTER (Tu cuadro verde) */ +.wizard-footer { + flex-shrink: 0; /* Prohibido encogerse */ + padding: 15px 45px 25px 45px; + background-color: #f0fdfa; + border-top: 1px solid rgba(50, 168, 155, 0.1); + margin-top: auto; +} + +/* ========================================= + TÍTULOS, INPUTS Y BOTONES GENERALES + ========================================= */ +.wizard-title { + color: #32a89b; + font-weight: 800; + font-size: 2.2rem; + margin-bottom: 20px; + text-align: center; +} + +.wizard-label { + display: block; + color: #32a89b; + font-weight: 700; + font-size: 0.85rem; + text-transform: uppercase; + margin-bottom: 8px; + margin-left: 15px; +} + +.wizard-input { + width: 100%; + height: 55px; + border-radius: 50px; + border: 2px solid #e2e8f0; + padding: 0 25px; + font-size: 1rem; + outline: none; + transition: border-color 0.3s; + background-color: white !important; + color: #475569 !important; +} + +.wizard-input:focus { + border-color: #32a89b; +} + +.btn-next { + background-color: #32a89b; + color: white; + border: none; + padding: 16px; + border-radius: 50px; + font-weight: bold; + width: 100%; + cursor: pointer; + transition: all 0.3s; +} + +.btn-next:hover:not(:disabled) { + background-color: #288a7f; + transform: translateY(-2px); +} + +.btn-next:disabled { + background-color: #a5d6d1; + cursor: not-allowed; +} + +.btn-back { + background-color: #e2e8f0; + color: #475569; + border: none; + padding: 16px; + border-radius: 50px; + font-weight: bold; + width: 100%; + cursor: pointer; +} + +/* SCROLLBAR PERSONALIZADO */ +.wizard-body::-webkit-scrollbar { width: 6px; } +.wizard-body::-webkit-scrollbar-thumb { background: #32a89b; border-radius: 10px; } + +/* ========================================= + ELEMENTOS DE TAREAS Y CUPONES (Items) + ========================================= */ +.task-input-row { display: flex; gap: 10px; align-items: center; } +.task-coin-input-wrapper { display: flex; align-items: center; background-color: white !important; border-radius: 50px; border: 2px solid #32a89b; padding: 0 15px; height: 55px; width: 110px; } +.task-coin-input { border: none; width: 100%; font-weight: bold; color: #32a89b !important; outline: none; text-align: center; background: transparent; } +.task-coin-input::-webkit-outer-spin-button, .task-coin-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } +.task-coin-input[type=number] { -moz-appearance: textfield; } + +.task-list-label { color: #059669 !important; margin-bottom: 15px; } +.empty-tasks-msg { text-align: center; color: #94a3b8 !important; font-size: 0.9rem; font-style: italic; margin-top: 10px; } + +.task-item { display: flex; align-items: center; background-color: white !important; border-radius: 50px; padding: 10px 20px; margin-bottom: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); border: 1px solid #e2e8f0; } +.task-name { flex: 1; font-weight: 600; color: #475569 !important; font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.task-days-container { display: flex; gap: 4px; margin: 0 15px; } +.task-day-bubble { cursor: pointer; font-size: 10px; width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; background-color: #f1f5f9; color: #94a3b8; transition: all 0.2s ease; } +.task-day-bubble.active { background-color: #32a89b !important; color: white !important; } +.task-coins-display { font-weight: 800; color: #f39c12 !important; min-width: 50px; } +.btn-delete-task { background: none; border: none; cursor: pointer; font-size: 1.1rem; padding: 0 5px; transition: transform 0.2s; } +.btn-delete-task:hover { transform: scale(1.2); } + +.footer-suggestion { text-align: center; color: #94a3b8 !important; font-size: 0.8rem; margin-bottom: 10px; } +.footer-buttons { display: flex; gap: 15px; margin-top: 15px; } \ No newline at end of file diff --git a/src/front/components/ChildProfileCreation/ChildWizard.jsx b/src/front/components/ChildProfileCreation/ChildWizard.jsx new file mode 100644 index 0000000..76aacab --- /dev/null +++ b/src/front/components/ChildProfileCreation/ChildWizard.jsx @@ -0,0 +1,158 @@ +import React, { useState } from "react"; +import { ChildRegistration } from "./ChildRegistration"; +import { ChildTaskSetting } from "./ChildTaskSetting"; +import { ChildSmallGoals } from "./ChildSmallGoals"; +import { ChildGrandPrizeSet } from "./ChildGrandPrizeSet"; +import { ChildSummary } from "./ChildSummary"; // ¡Importante importar el nuevo componente! +import "./ChildWizard.css"; + +export const ChildWizard = ({ onClose }) => { + const [step, setStep] = useState(1); + const [isSaving, setIsSaving] = useState(false); // Para mostrar "Guardando..." en el paso 5 + const [saveError, setSaveError] = useState(null); // Por si falla el backend + const [formData, setFormData] = useState({ + child: null, + tasks: [], + smallGoals: [], + grandPrize: null + }); + + // Esta función solo avanza de paso y guarda datos locales (del paso 1 al 4) + const handleNext = (newData) => { + setFormData(prev => ({ ...prev, ...newData })); + setStep(step + 1); + }; + + const handleBack = () => setStep(step - 1); + + // Esta función se dispara cuando el paso 4 llama a onNextStep + const handleTransitionToSummary = (finalGrandPrizeData) => { + // 1. Guardamos el premio en el estado + const updatedData = { ...formData, grandPrize: finalGrandPrizeData }; + setFormData(updatedData); + // 2. Avanzamos al paso 5 para que el usuario vea la pantalla final + setStep(5); + // 3. Ejecutamos la subida a base de datos en segundo plano + handleFinalSubmit(updatedData); + }; + + const handleFinalSubmit = async (fullData) => { + setIsSaving(true); + setSaveError(null); + const rawUrl = import.meta.env.VITE_BACKEND_URL || ""; + const baseUrl = rawUrl.replace(/\/$/, "").replace("3000", "3001"); + const token = localStorage.getItem("token"); + + const getHeaders = () => { + const headers = { "Content-Type": "application/json" }; + if (token && token !== "null" && token !== "undefined") { + headers["Authorization"] = `Bearer ${token}`; + } + return headers; + }; + + try { + // 1. Crear el Niño + const childData = { + name: fullData.child.child.name, + age: fullData.child.child.age, + pin: fullData.child.child.pin, + avatar: fullData.child.child.avatar || "default_avatar.png" + }; + + const childResponse = await fetch(`${baseUrl}/api/child`, { + method: "POST", + headers: getHeaders(), + body: JSON.stringify(childData) + }); + + if (!childResponse.ok) { + const errorData = await childResponse.json(); + throw new Error(errorData.message || "Fallo al crear el perfil"); + } + + const childResult = await childResponse.json(); + const childId = childResult.child.id; + + // 2. Crear todo lo demás en paralelo + await Promise.all([ + fetch(`${baseUrl}/api/child/${childId}/tasks`, { + method: "POST", + headers: getHeaders(), + body: JSON.stringify(fullData.tasks) + }), + fetch(`${baseUrl}/api/child/${childId}/small-goals`, { + method: "POST", + headers: getHeaders(), + body: JSON.stringify(fullData.smallGoals) + }), + fetch(`${baseUrl}/api/child/${childId}/grand-prize`, { + method: "POST", + headers: getHeaders(), + body: JSON.stringify({ + name: fullData.grandPrize.name, + coins: parseInt(fullData.grandPrize.coins), + image_url: fullData.grandPrize.image_url || "" + }) + }) + ]); + + // Si todo va bien, quitamos el estado de "Cargando" + setIsSaving(false); + + } catch (error) { + console.error("❌ ERROR CRÍTICO BACKEND:", error.message); + setSaveError(error.message); + setIsSaving(false); + } + }; + + return ( +
+ {step === 1 && ( + handleNext({ child: childData })} + onClose={onClose} + /> + )} + + {step === 2 && ( + handleNext({ tasks: tasksData })} + /> + )} + + {step === 3 && ( + handleNext({ smallGoals: goalsData })} + /> + )} + + {step === 4 && ( + + )} + + {step === 5 && ( + + )} +
+ ); +}; + diff --git a/src/front/components/ChildProfileCreation/ProgressBar.jsx b/src/front/components/ChildProfileCreation/ProgressBar.jsx new file mode 100644 index 0000000..2f8ca25 --- /dev/null +++ b/src/front/components/ChildProfileCreation/ProgressBar.jsx @@ -0,0 +1,25 @@ +import React from "react"; + +export const ProgressBar = ({ step }) => { + const percentage = (step / 4) * 100; + + return ( +
+ {/* Contenedor de Bootstrap */} +
+
+
+

+ PASO {step} DE 4 +

+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/GoalSection.jsx b/src/front/components/GoalSection.jsx new file mode 100644 index 0000000..642d906 --- /dev/null +++ b/src/front/components/GoalSection.jsx @@ -0,0 +1,39 @@ +import React from "react"; + +export const GoalSection = ({ child }) => { + return ( +
+

Gran Premio

+ +
+

{child.goal}

+ +
+
🎮
+ +
+ 10.000 + Monedas +
+
+ +
+
+
+ +

+ {child.progress}% completado +

+ +

+ Sigue completando tareas para acercarte a tu meta. +

+ + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/PinModal.jsx b/src/front/components/PinModal.jsx new file mode 100644 index 0000000..dc55246 --- /dev/null +++ b/src/front/components/PinModal.jsx @@ -0,0 +1,45 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import ReactDOM from "react-dom"; + +export const PinModal = ({ profile, onClose }) => { + const [pin, setPin] = useState(""); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = () => { + if (pin === profile.pin) { + setError(""); + // Redirección según rol + if (profile.role === "parent") { + navigate("/parentadmin"); + } else { + navigate("/ChildDashboard"); // <-- ruta correcta para hijos + } + onClose(); + } else { + setError("PIN incorrecto, intenta de nuevo"); + } + }; + + return ReactDOM.createPortal( +
+
e.stopPropagation()}> +

Ingresa el PIN de {profile.name}

+ setPin(e.target.value)} + placeholder="••••" + /> + {error &&

{error}

} +
+ + +
+
+
, + document.body + ); +}; \ No newline at end of file diff --git a/src/front/components/ProfileCard.jsx b/src/front/components/ProfileCard.jsx new file mode 100644 index 0000000..d5432fb --- /dev/null +++ b/src/front/components/ProfileCard.jsx @@ -0,0 +1,10 @@ + + +export const ProfileCard = ({ name, avatar, onClick }) => { + return ( +
+ {name} +

{name}

+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/RewardModal.jsx b/src/front/components/RewardModal.jsx new file mode 100644 index 0000000..8a43300 --- /dev/null +++ b/src/front/components/RewardModal.jsx @@ -0,0 +1,34 @@ +import React from "react"; + +export const RewardModal = ({ rewards, coins, onClose, onRedeem }) => { + return ( +
+
e.stopPropagation()}> + + +

Tienda de recompensas 🎟️

+ +
+ {rewards && rewards.length > 0 ? ( + rewards.map(reward => ( +
+
🎁
+

{reward.title}

+

🪙 {reward.cost}

+ +
+ )) + ) : ( +

Sin recompensas disponibles

+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/TaskModal.jsx b/src/front/components/TaskModal.jsx new file mode 100644 index 0000000..7076235 --- /dev/null +++ b/src/front/components/TaskModal.jsx @@ -0,0 +1,35 @@ +import React from "react"; + +export const TaskModal = ({ tasks, onClose, onComplete }) => { + return ( +
+
e.stopPropagation()}> + + +

Tareas de casa

+ +
+ {tasks && tasks.length > 0 ? ( + tasks.map(task => ( +
+
🏠
+
+

{task.title}

+

🪙 +{task.coins}

+
+ +
+ )) + ) : ( +

Sin tareas disponibles

+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/TaskSection.jsx b/src/front/components/TaskSection.jsx new file mode 100644 index 0000000..16c2595 --- /dev/null +++ b/src/front/components/TaskSection.jsx @@ -0,0 +1,26 @@ +import React from "react"; + +export const TaskSection = ({ tasks }) => { + const firstTask = tasks?.[0]; + + return ( +
+
+ 2 +

Tareas de casa

+
+ +
+
🐶
+ +

+ {firstTask ? firstTask.title : "Sin tareas disponibles"} +

+ +

+ +{firstTask ? firstTask.coins : 0} 🪙 +

+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/data/mockUsers.js b/src/front/data/mockUsers.js new file mode 100644 index 0000000..0e38ad7 --- /dev/null +++ b/src/front/data/mockUsers.js @@ -0,0 +1,20 @@ +export const mockUsers = [ + { + id: 1, + username: "papa@example.com", + password: "123456", // solo para pruebas + role: "parent", + profileName: "Papá", + avatar: "/assets/img/Cashtor.jpg", + pin: "1234" + }, + { + id: 2, + username: "dani@example.com", + password: "abcdef", + role: "child", + profileName: "Dani", + avatar: "/assets/img/avatar2.png", + pin: "0000" + } +]; \ No newline at end of file diff --git a/src/front/index.css b/src/front/index.css index 81b61ea..f034ced 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -11,6 +11,15 @@ --page-muted: #506164; --page-highlight: #ef8354; --page-shadow: 0 24px 60px rgba(35, 47, 53, 0.12); + + --titular1:#29a38d; + --text-btn:#3dc9b6; + --action-btn: #3dc9b6; + --disable-btn1: #b4b4b4; + --disable-btn2: #d9d9d9; + + + } * { diff --git a/src/front/pages/ChildDashboard.jsx b/src/front/pages/ChildDashboard.jsx new file mode 100644 index 0000000..51bd86e --- /dev/null +++ b/src/front/pages/ChildDashboard.jsx @@ -0,0 +1,172 @@ +import React, { useEffect, useState } from "react"; +import { ChildHeader } from "../components/ChildHeader"; +import { GoalSection } from "../components/GoalSection"; +import { TaskSection } from "../components/TaskSection"; +import { getChildDashboard } from "../services/childDashboard"; +import "../styles/child-dashboard.css"; +import { TaskModal } from "../components/TaskModal"; +import { RewardModal } from "../components/RewardModal"; + +export const ChildDashboard = () => { + const [data, setData] = useState(null); + const [error, setError] = useState(false); + const [showTaskModal, setShowTaskModal] = useState(false); + const [showRewardModal, setShowRewardModal] = useState(false); + + useEffect(() => { + const loadData = async () => { + const result = await getChildDashboard(2); + + if (!result) { + setError(true); + return; + } + + setData(result); + }; + + loadData(); + }, []); + + if (error) { + return ( +
+
+
+
+ No se pudo cargar el dashboard del hijo. +
+
+
+
+ ); + } + + if (!data) { + return ( +
+
+
+
+
+

Cargando tu progreso...

+
+
+
+
+ ); + } + + const { child, tasks } = data; + + const handleComplete = async (taskId) => { + const response = await fetch( + `${import.meta.env.VITE_BACKEND_URL}/api/tasks/${taskId}/complete`, + { method: "PATCH" } + ); + if (response.ok) { + const result = await getChildDashboard(2); + if (result) setData(result); + } + }; + + const handleRedeem = async (rewardId) => { + const response = await fetch( + `${import.meta.env.VITE_BACKEND_URL}/api/rewards/${rewardId}/redeem`, + { method: "POST" } + ); + if (response.ok) { + const result = await getChildDashboard(2); + if (result) setData(result); + } + }; + + return ( +
+
+ + +
+
+
+

+ ¡Hola, {child.name}! +

+ +
+
+
+

+ Tu racha de {child.streak} días seguidos 🔥 +

+
+ +
+
+ {["Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"].map((dia, index) => ( +
+ + {dia} +
+ ))} +
+ +
🪙
+
+
+
+ +
+
setShowRewardModal(true)} + style={{ cursor: "pointer" }} + > +
+ 1 +

+ Tienda +

+
+ +
+
🎟️
+

+ Entradas al cine y más +

+
+
+ {showRewardModal && ( + setShowRewardModal(false)} + onRedeem={handleRedeem} + /> + )} + +
setShowTaskModal(true)} style={{ cursor: "pointer" }}> + +
+ {showTaskModal && ( + setShowTaskModal(false)} + onComplete={handleComplete} + /> + )} +
+
+
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Login.jsx b/src/front/pages/Login.jsx new file mode 100644 index 0000000..017fa44 --- /dev/null +++ b/src/front/pages/Login.jsx @@ -0,0 +1,95 @@ +import { useState } from "react"; +import "../Login.css"; + +// 👇 IMPORTS CORRECTOS +import beaverImg from "../assets/img/cashtor_coins.png"; + +export const Login = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const handleLogin = async () => { + setError(""); + setLoading(true); + const rawUrl = import.meta.env.VITE_BACKEND_URL || ""; + try { + const res = await fetch( + "https://supreme-enigma-w4q77jgrqwv29q99-3001.app.github.dev/api/sign-in", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + } + ); + + const data = await res.json(); + + if (!res.ok) { + setError(data.message || "Credenciales incorrectas"); + return; + } + + localStorage.setItem("token", data.access_token); + localStorage.setItem("user", JSON.stringify(data.user)); + + window.location.href = "/profile-selection"; + } catch (err) { + console.error(err); + setError("No se pudo conectar con el backend"); + } finally { + setLoading(false); + } + }; + + const handleSubmit = (e) => { + e.preventDefault(); + handleLogin(); + }; + + return ( +
+ + {/* Card izquierda */} +
+

¡Bienvenido de nuevo!

+ +
+ setEmail(e.target.value)} + required + /> + + setPassword(e.target.value)} + required + /> + + + + {error &&

{error}

} + +

+ ¿No tienes cuenta?
Regístrate +

+
+
+ + {/* Imagen derecha */} +
+ Cashtor +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Orders.jsx b/src/front/pages/Orders.jsx deleted file mode 100644 index 2c1c2d8..0000000 --- a/src/front/pages/Orders.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import { useEffect } from "react"; -import { Link, useNavigate } from "react-router-dom"; - -import useGlobalReducer from "../hooks/useGlobalReducer"; -import { apiRequest, authHeaders } from "../services/api"; - - -export const Orders = () => { - const { store, dispatch } = useGlobalReducer(); - const navigate = useNavigate(); - - useEffect(() => { - let isMounted = true; - - const loadOrders = async () => { - dispatch({ type: "orders_request" }); - - try { - const data = await apiRequest("/api/orders", { - headers: authHeaders(store.token) - }); - - if (!isMounted) { - return; - } - - dispatch({ - type: "orders_success", - payload: data.orders - }); - } catch (error) { - if (!isMounted) { - return; - } - - if (error.status === 401) { - dispatch({ - type: "clear_session", - payload: "Tu sesion ya no es valida. Entra otra vez." - }); - navigate("/sign-in", { replace: true }); - return; - } - - dispatch({ - type: "orders_failure", - payload: error.message - }); - } - }; - - loadOrders(); - - return () => { - isMounted = false; - }; - }, [dispatch, navigate, store.token]); - - const totalSpent = store.orders.reduce( - (accumulator, order) => accumulator + order.total_price, - 0 - ); - - return ( -
-
-
-
-

Protected page

-

Mis ordenes

-
-

- Esta pagina consulta /api/orders y muestra solo las compras del usuario autenticado. -

-
- -
-
-
- Total de ordenes - {store.orders.length} -
-
-
-
- Monto acumulado - ${totalSpent.toFixed(2)} -
-
-
-
- Usuario actual - {store.user?.name} -
-
-
- - {store.loading.orders ? ( -
-

Cargando ordenes...

-
- ) : null} - {store.errors.orders ? ( -
{store.errors.orders}
- ) : null} - - {!store.loading.orders && !store.orders.length ? ( -
-

Aun no tienes compras

-

- Puedes volver al catalogo publico y crear una orden autenticada desde ahi. -

- - Volver al catalogo - -
- ) : null} - -
- {store.orders.map((order) => ( -
-
- {order.product.name} -
-
-
-

{order.product.category}

-

{order.product.name}

-

{order.product.description}

-
- {order.status} -
-
- Cantidad: {order.quantity} - Unitario: ${order.unit_price.toFixed(2)} - Total: ${order.total_price.toFixed(2)} - Fecha: {new Date(order.created_at).toLocaleDateString()} -
-
-
-
- ))} -
-
-
- ); -}; diff --git a/src/front/pages/ProfilesPage.jsx b/src/front/pages/ProfilesPage.jsx new file mode 100644 index 0000000..85ec1d7 --- /dev/null +++ b/src/front/pages/ProfilesPage.jsx @@ -0,0 +1,43 @@ +import { useState } from "react"; +import { ProfileCard } from "../components/ProfileCard"; +import { PinModal } from "../components/PinModal"; + +// Importar CSS +import "../ProfilesPage.css"; + +// Importar imágenes desde src +import Cashtor from "../assets/img/Cashtor.jpg"; +import CashtorCoins from "../assets/img/cashtor_coins.png"; // ejemplo si quieres otro avatar + +export const ProfilesPage = () => { + const [selectedProfile, setSelectedProfile] = useState(null); + +const profiles = [ + { id: 1, name: "Papá", avatar: Cashtor, pin: "1234", role: "parent" }, + { id: 2, name: "Dani", avatar: CashtorCoins, pin: "4321", role: "child" } +]; + + return ( +
+

¿Quién está usando FinQuest?

+ +
+ {profiles.map((profile) => ( + setSelectedProfile(profile)} + /> + ))} +
+ + {selectedProfile && ( + setSelectedProfile(null)} + /> + )} +
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/SignIn.jsx b/src/front/pages/SignIn.jsx index 75b3d69..e766012 100644 --- a/src/front/pages/SignIn.jsx +++ b/src/front/pages/SignIn.jsx @@ -4,11 +4,11 @@ import { Link, Navigate, useLocation, useNavigate } from "react-router-dom"; import useGlobalReducer from "../hooks/useGlobalReducer"; import { apiRequest } from "../services/api"; - export const SignIn = () => { const { store, dispatch } = useGlobalReducer(); const location = useLocation(); const navigate = useNavigate(); + const [formData, setFormData] = useState({ email: "lara@example.com", password: "demo123" @@ -45,11 +45,14 @@ export const SignIn = () => { user: data.user } }); + dispatch({ - type: "set_notice", - payload: `Bienvenido otra vez, ${data.user.name}.` - }); - navigate(redirectTarget, { replace: true }); + type: "set_notice", + payload: `Bienvenido otra vez, ${data.user.name}.` +}); + +// Redirección fija +window.location.href = "/profiles"; } catch (error) { dispatch({ type: "auth_failure", @@ -59,58 +62,62 @@ export const SignIn = () => { }; return ( -
-
-
-
-
-

Access private pages

-

Sign in

-

- Al iniciar sesion podras consultar /api/me y tus ordenes en /api/orders. -

-
- - - - - - - {store.errors.auth ? ( -
{store.errors.auth}
- ) : null} - - -
- -

- Si aun no tienes cuenta, puedes{" "} - crear un usuario nuevo. -

+
+ + {/* CARD LOGIN */} +
+

Iniciar sesión

+ +
+ + + + + + {store.errors.auth && ( +
+ {store.errors.auth}
-
+ )} + + + + +
+

+ ¿No tienes cuenta?{" "} + Regístrate +

-
+ + {/* IMAGEN DERECHA */} +
+ Niños aprendiendo finanzas +
+ + ); -}; +}; \ No newline at end of file diff --git a/src/front/routes.jsx b/src/front/routes.jsx index c686b4b..ff19f60 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -5,14 +5,18 @@ import { } from "react-router-dom"; import { PrivateRoute } from "./components/PrivateRoute"; +import { ParentAdmin } from "../ParentDashboard/pages/ParentAdmin"; import { Home } from "./pages/Home"; import { Layout } from "./pages/Layout"; import { NotFound } from "./pages/NotFound"; -import { Orders } from "./pages/Orders"; import { Profile } from "./pages/Profile"; import { SignIn } from "./pages/SignIn"; import { SignUp } from "./pages/SignUp"; - +import { ChildDashboard } from "./pages/ChildDashboard"; +import { Login } from "./pages/Login"; +import { ProfilesPage } from "./pages/ProfilesPage.jsx"; +import { ChildRegistration } from "./components/ChildProfileCreation/ChildRegistration.jsx"; +import { ChildWizard } from "./components/ChildProfileCreation/ChildWizard.jsx"; export const router = createBrowserRouter( createRoutesFromElements( @@ -20,6 +24,20 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> + } /> + {/* + + + } + /> */} + + {/* Ahora está público, pero luego será privado con login */} + } /> + )} /> - )} + /> */} + } /> + + {/* 🎯 RUTA DEL WIZARD: Aquí es donde ocurre la magia de los 3 pasos */} + } /> + } /> - ) -); + ), + { + future: { + v7_startTransition: true, + v7_relativeSplatPath: true, + v7_fetcherPersist: true, + v7_normalizeFormMethod: true, + v7_partialHydration: true, + v7_skipActionErrorRevalidation: true + } + } +); \ No newline at end of file diff --git a/src/front/services/childDashboard.js b/src/front/services/childDashboard.js new file mode 100644 index 0000000..8c948fe --- /dev/null +++ b/src/front/services/childDashboard.js @@ -0,0 +1,17 @@ +export const getChildDashboard = async (childId) => { + try { + const response = await fetch( + `${import.meta.env.VITE_BACKEND_URL}/api/child-dashboard/${childId}` + ); + + if (!response.ok) { + throw new Error("Error al obtener el dashboard"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(error); + return null; + } +}; \ No newline at end of file diff --git a/src/front/styles/child-dashboard.css b/src/front/styles/child-dashboard.css new file mode 100644 index 0000000..de67467 --- /dev/null +++ b/src/front/styles/child-dashboard.css @@ -0,0 +1,585 @@ +.child-dashboard { + min-height: 100vh; + background: #eaf3f7; + padding: 24px 18px 40px; + font-family: Arial, sans-serif; + color: #1f1f1f; +} + +.child-dashboard__container { + max-width: 1180px; + margin: 0 auto; +} + +.child-dashboard__content { + display: grid; + grid-template-columns: 1.05fr 0.95fr; + gap: 22px; + margin-top: 22px; +} + +.child-dashboard__state { + min-height: 60vh; + display: flex; + align-items: center; + justify-content: center; +} + +.child-dashboard__message { + background: #ffffff; + border-radius: 24px; + padding: 28px; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); +} + +.child-dashboard__message--error { + color: #b42318; +} + +.child-dashboard__spinner { + width: 42px; + height: 42px; + border: 4px solid #d9e4ea; + border-top-color: #1f8ca8; + border-radius: 50%; + margin: 0 auto 14px; + animation: spin 0.9s linear infinite; +} + +.child-topbar { + background: #dfe8ed; + border-radius: 22px; + padding: 22px 26px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 22px; +} + +.child-topbar__profile { + display: flex; + align-items: center; + gap: 14px; + min-width: 240px; +} + +.child-topbar__avatar { + width: 52px; + height: 52px; + border-radius: 50%; + background: #fff3d6; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); +} + +.child-topbar__label { + margin: 0 0 4px; + font-size: 0.85rem; + color: #5a5a5a; +} + +.child-topbar__name { + margin: 0; + font-size: 1.9rem; + font-weight: 800; +} + +.child-topbar__stats { + display: flex; + gap: 28px; + flex: 1; + justify-content: flex-end; + flex-wrap: wrap; +} + +.child-topbar__stat { + min-width: 260px; +} + +.child-topbar__stat-head { + margin-bottom: 8px; +} + +.child-topbar__stat-title { + font-size: 1.1rem; + font-weight: 800; +} + +.progress-track { + width: 100%; + height: 14px; + border-radius: 999px; + background: #cfe0e0; + overflow: hidden; +} + +.progress-track--large { + height: 16px; + margin-top: 12px; +} + +.progress-fill { + height: 100%; + border-radius: 999px; +} + +.progress-fill--level { + background: linear-gradient(90deg, #0c7b9d 0%, #38c8c2 60%, #d6d719 100%); +} + +.progress-fill--coins { + background: linear-gradient(90deg, #d4af00 0%, #f0d429 100%); +} + +.progress-fill--goal { + background: linear-gradient(90deg, #0c7b9d 0%, #38c8c2 60%, #d4c900 100%); +} + +.dashboard-panel, +.goal-card { + background: #eef2f4; + border-radius: 24px; + padding: 26px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); +} + +.dashboard-panel__title, +.goal-card__title { + margin: 0; + font-size: 1.9rem; + font-weight: 800; +} + +.dashboard-panel__top { + margin-top: 18px; + margin-bottom: 22px; +} + +.dashboard-panel__bottom { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 22px; +} + +.dashboard-placeholder, +.task-summary-card { + background: #ffffff; + border-radius: 24px; + padding: 22px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); +} + +.dashboard-placeholder--streak { + min-height: 150px; +} + +.dashboard-placeholder--shop, +.task-summary-card { + min-height: 280px; +} + +.dashboard-placeholder__streak-header { + display: flex; + justify-content: center; + margin-bottom: 18px; +} + +.dashboard-placeholder__header, +.task-summary-card__header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 18px; +} + +.dashboard-placeholder__badge, +.task-summary-card__badge { + width: 38px; + height: 38px; + border-radius: 50%; + color: #ffffff; + font-weight: 800; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.dashboard-placeholder__badge { + background: #27b3a3; +} + +.task-summary-card__badge { + background: #a978df; +} + +.dashboard-placeholder__title, +.task-summary-card__title { + margin: 0; + font-size: 1.6rem; + font-weight: 800; +} + +.dashboard-placeholder__text { + margin: 0; + color: #5e5e5e; + font-weight: 600; +} + +.dashboard-streak { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; +} + +.dashboard-streak__days { + display: flex; + gap: 14px; + flex-wrap: wrap; +} + +.dashboard-streak__day { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.dashboard-streak__check { + width: 42px; + height: 42px; + border-radius: 50%; + background: #d9d9d9; + color: #ffffff; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1.6rem; + font-weight: 800; +} + +.dashboard-streak__day--active .dashboard-streak__check { + background: #24b8a8; +} + +.dashboard-streak__label { + font-size: 0.95rem; + color: #7a7a7a; + font-weight: 500; +} + +.dashboard-streak__coins { + font-size: 4rem; + line-height: 1; +} + +.dashboard-shop { + min-height: 190px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 14px; + text-align: center; +} + +.dashboard-shop__image { + font-size: 5rem; + line-height: 1; +} + +.dashboard-shop__text { + margin: 0; + font-size: 1.1rem; + color: #1f1f1f; +} + +.task-summary-card__content { + min-height: 180px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + text-align: center; +} + +.task-summary-card__image { + font-size: 84px; + line-height: 1; + margin-bottom: 6px; +} + +.task-summary-card__task-name { + margin: 0; + font-size: 1.15rem; + font-weight: 500; +} + +.task-summary-card__reward { + margin: 0; + font-size: 2rem; + font-weight: 900; + color: #20b8a7; +} + +.goal-card__box { + margin-top: 14px; + background: #ffffff; + border-radius: 28px; + padding: 24px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); +} + +.goal-card__name { + margin: 0 0 18px; + font-size: 1.2rem; + font-weight: 800; +} + +.goal-card__hero { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; +} + +.goal-card__image { + font-size: 72px; + line-height: 1; +} + +.goal-card__price { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.goal-card__price-number { + font-size: 3rem; + font-weight: 900; + line-height: 1; +} + +.goal-card__price-label { + font-size: 1.2rem; + font-weight: 800; +} + +.goal-card__progress-text { + margin: 14px 0 10px; + text-align: center; + font-size: 1.1rem; +} + +.goal-card__hint { + margin: 0 0 22px; + text-align: center; + color: #24a598; + font-weight: 700; +} + +.goal-card__button { + width: 100%; + border: none; + border-radius: 18px; + padding: 18px 20px; + background: #48c3b8; + color: #ffffff; + font-size: 1.1rem; + font-weight: 800; + cursor: pointer; +} + +.goal-card__button:hover { + opacity: 0.95; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@media (max-width: 980px) { + .child-dashboard__content { + grid-template-columns: 1fr; + } + + .child-topbar { + flex-direction: column; + align-items: stretch; + } + + .child-topbar__stats { + justify-content: stretch; + } + + .child-topbar__stat { + min-width: unset; + } +} + +@media (max-width: 900px) { + .dashboard-panel__bottom { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .child-dashboard { + padding: 14px 10px 28px; + } + + .dashboard-panel, + .goal-card, + .goal-card__box, + .dashboard-placeholder, + .task-summary-card { + border-radius: 20px; + } + + .dashboard-panel__title, + .goal-card__title, + .dashboard-placeholder__title, + .task-summary-card__title { + font-size: 1.5rem; + } + + .goal-card__hero { + flex-direction: column; + align-items: flex-start; + } + + .goal-card__price-number { + font-size: 2.4rem; + } + + .child-topbar__name { + font-size: 1.5rem; + } + + .dashboard-streak { + flex-direction: column; + align-items: flex-start; + } + + .dashboard-streak__days { + gap: 10px; + } + + .dashboard-streak__check { + width: 36px; + height: 36px; + font-size: 1.2rem; + } + + .dashboard-streak__coins { + font-size: 3rem; + } +} + +.task-modal__overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.task-modal { + background: #eaf3f7; + border-radius: 24px; + padding: 32px; + width: 90%; + max-width: 860px; + max-height: 85vh; + overflow-y: auto; + position: relative; +} + +.task-modal__close { + position: absolute; + top: 18px; + right: 22px; + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #1f1f1f; +} + +.task-modal__title { + text-align: center; + font-size: 2rem; + font-weight: 800; + margin: 0 0 24px; +} + +.task-modal__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; +} + +.task-modal__item { + background: #ffffff; + border-radius: 18px; + padding: 18px; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + text-align: center; +} + +.task-modal__item-image { + font-size: 3rem; +} + +.task-modal__item-name { + margin: 0; + font-size: 1rem; + font-weight: 700; +} + +.task-modal__item-coins { + margin: 0; + font-size: 1rem; + font-weight: 800; + color: #20b8a7; +} + +.task-modal__item-btn { + width: 48px; + height: 48px; + border-radius: 50%; + border: none; + background: #48c3b8; + color: #ffffff; + font-size: 1.4rem; + font-weight: 800; + cursor: pointer; +} + +.task-modal__item-btn--done { + background: #a3d9a5; + cursor: default; +} + +@media (max-width: 640px) { + .task-modal__grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.task-modal__item-btn--disabled { + background: #d9d9d9; + cursor: not-allowed; +}