diff --git a/.vscode/settings.json b/.vscode/settings.json index ba8c997..db43021 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,11 +31,18 @@ "source.organizeImports": "explicit" } }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": [ + "typescript", + "typescriptreact" + ], "prettier.requireConfig": true, "files.eol": "\n", - "python.testing.pytestArgs": ["backend"], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "python.analysis.typeCheckingMode": "strict", - "python.analysis.extraPaths": ["${workspaceFolder}/backend"], -} \ No newline at end of file + "python.testing.pytestArgs": ["."], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.analysis.typeCheckingMode": "strict", + "python.analysis.extraPaths": ["${workspaceFolder}/backend"] +} diff --git a/backend/app/api/routers/tasks.py b/backend/app/api/routers/tasks.py index 4701d83..38c8f09 100644 --- a/backend/app/api/routers/tasks.py +++ b/backend/app/api/routers/tasks.py @@ -36,7 +36,12 @@ async def ingest_file( session: Session = Depends(get_db), ) -> JobResponse: assert user_id - allowed_content_types: list[str] = ["image/jpeg", "image/png", "application/pdf"] + allowed_content_types: list[str] = [ + "image/jpeg", + "image/png", + "application/pdf", + "text/plain", + ] content_type: str | None = file.content_type if content_type is None: raise HTTPException(status_code=400, detail="No valid file content type") diff --git a/backend/tests/test_api/test_tasks.py b/backend/tests/test_api/test_tasks.py index d49118d..1555806 100644 --- a/backend/tests/test_api/test_tasks.py +++ b/backend/tests/test_api/test_tasks.py @@ -38,13 +38,32 @@ def test_ingest_file_invalid_content_type( ) -> None: """Test file ingestion fails with invalid content type.""" file_content = b"fake content" - files = {"file": ("test.txt", file_content, "text/plain")} + files = {"file": ("test.json", file_content, "application/json")} response = client.post("/tasks/ingest/file", files=files) assert response.status_code == 400 assert "Invalid file content type" in response.json()["detail"] + @patch("app.api.routers.tasks.ingest_file_task") + def test_ingest_txt_content_type( + self, + mock_ingest_task: MagicMock, + client: TestClient, + mock_user_id: int, + ) -> None: + """Test file ingestion fails with invalid content type.""" + mock_job = MagicMock() + mock_job.id = "test-job-id-txt" + mock_ingest_task.delay.return_value = mock_job + + file_content = b"fake content" + files = {"file": ("test.txt", file_content, "text/plain")} + + response = client.post("/tasks/ingest/file", files=files) + + assert response.status_code == 200 + @patch("app.api.routers.tasks.ingest_file_task") def test_ingest_file_png_success( self, diff --git a/frontend/package.json b/frontend/package.json index ad54e41..1e9becf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.9", + "eslint-plugin-react-hooks": "^7.0.1", "prettier": "^3.7.4", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 52aa4d7..a675610 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -70,7 +70,7 @@ importers: version: 0.562.0(react@19.1.0) next: specifier: 15.5.9 - version: 15.5.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 15.5.9(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -117,6 +117,9 @@ importers: eslint-config-next: specifier: 15.5.9 version: 15.5.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) prettier: specifier: ^3.7.4 version: 3.7.4 @@ -136,6 +139,73 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} @@ -1263,6 +1333,11 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1273,6 +1348,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1322,6 +1402,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1393,6 +1476,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1432,6 +1518,10 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1504,6 +1594,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -1614,6 +1710,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1683,6 +1783,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1827,6 +1933,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1840,6 +1951,11 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -1943,6 +2059,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.562.0: resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} peerDependencies: @@ -2016,6 +2135,9 @@ packages: sass: optional: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2407,6 +2529,12 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2455,10 +2583,19 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@4.2.1: resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} @@ -2466,6 +2603,106 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@date-fns/tz@1.4.1': {} '@emnapi/core@1.7.1': @@ -3553,6 +3790,8 @@ snapshots: balanced-match@1.0.2: {} + baseline-browser-mapping@2.10.0: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3566,6 +3805,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001761 + electron-to-chromium: 1.5.302 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3620,6 +3867,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3688,6 +3937,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.302: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.18.4: @@ -3796,6 +4047,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-next@15.5.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): @@ -3904,6 +4157,17 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.2(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.2.1 + zod-validation-error: 4.0.2(zod@4.2.1) + transitivePeerDependencies: + - supports-color + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -4055,6 +4319,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4126,6 +4392,12 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4276,6 +4548,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -4286,6 +4560,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -4369,6 +4645,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-react@0.562.0(react@19.1.0): dependencies: react: 19.1.0 @@ -4409,7 +4689,7 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - next@15.5.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.5.9(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 @@ -4417,7 +4697,7 @@ snapshots: postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(react@19.1.0) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -4432,6 +4712,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-releases@2.0.27: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4811,10 +5093,12 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(react@19.1.0): + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 + optionalDependencies: + '@babel/core': 7.29.0 supports-color@7.2.0: dependencies: @@ -4924,6 +5208,12 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -4990,6 +5280,12 @@ snapshots: word-wrap@1.2.5: {} + yallist@3.1.1: {} + yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@4.2.1): + dependencies: + zod: 4.2.1 + zod@4.2.1: {} diff --git a/frontend/src/app/(dashboard)/page.tsx b/frontend/src/app/(dashboard)/page.tsx index b71e1fa..1861af4 100644 --- a/frontend/src/app/(dashboard)/page.tsx +++ b/frontend/src/app/(dashboard)/page.tsx @@ -1,10 +1,206 @@ "use client"; +import Link from "next/link"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { + ArrowRight, + Calendar, + CheckCircle2, + Clock, + FileText, + Settings, + Sparkles, + Target, + Zap, +} from "lucide-react"; + export default function Home() { return ( -
-

Chrono Guide

-

Welcome to Chrono Guide

+
+
+
+ {/* Hero Section */} +
+
+ + AI-Powered Task Management +
+

+ Welcome to Chrono Guide +

+

+ Transform your content into actionable tasks with intelligent + scheduling and time management +

+
+ + {/* Quick Actions */} +
+ + +
+ +
+
+ +
+
+ Create Tasks + + Upload files or enter text to extract tasks + +
+
+
+ + + +
+ +
+ + + +
+ +
+
+ +
+
+ View Schedule + + See your scheduled tasks and availability + +
+
+
+ + + +
+ +
+ + + +
+ +
+
+ +
+
+ Settings + + Configure your preferences and availability + +
+
+
+ + + +
+ +
+
+ + {/* Features Section */} +
+ + +
+
+ +
+ AI-Powered +
+
+ +

+ Automatically extract tasks from your content using advanced + AI +

+
+
+ + + +
+
+ +
+ Smart Scheduling +
+
+ +

+ Intelligent task scheduling based on your availability and + priorities +

+
+
+ + + +
+
+ +
+ Time Management +
+
+ +

+ Track time estimates and deadlines for better planning +

+
+
+ + + +
+
+ +
+ Stay Organized +
+
+ +

+ Keep track of drafts, scheduled, and completed tasks +

+
+
+
+
+
); } diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx index 93dfec2..7dc0e6a 100644 --- a/frontend/src/components/app-sidebar.tsx +++ b/frontend/src/components/app-sidebar.tsx @@ -4,85 +4,128 @@ import { Sidebar, SidebarContent, SidebarGroup, - SidebarTrigger, - SidebarGroupContent, SidebarMenuItem, SidebarMenuButton, SidebarHeader, SidebarFooter, + SidebarMenu, useSidebar, } from "@/components/ui/sidebar"; -import { Home, List, Settings, Calendar, LogOut } from "lucide-react"; +import { + Home, + List, + Settings, + Calendar, + LogOut, + Clock, + PanelLeft, +} from "lucide-react"; import { useAuth } from "@/context/auth-context"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; const sidebar_items = [ { label: "Dashboard", href: "/", - icon: , + icon: Home, }, { label: "Tasks", href: "/tasks", - icon: , + icon: List, }, { label: "Schedule", href: "/schedule", - icon: , + icon: Calendar, }, { label: "Settings", href: "/settings", - icon: , + icon: Settings, }, ]; export function AppSidebar() { - const { state } = useSidebar(); const { logout } = useAuth(); - const isCollapsed = state === "collapsed"; + const pathname = usePathname(); const handleLogout = async () => { await logout(); }; + const isActive = (href: string) => { + if (href === "/") { + return pathname === "/"; + } + return pathname.startsWith(href); + }; + + const { toggleSidebar } = useSidebar(); + return ( - - -
- - {!isCollapsed && ( -
- Chrono Guide - - Task Scheduler - -
- )} + +
+
+
- - - - {sidebar_items.map((item) => ( - - - - {item.icon} - {item.label} - - - - ))} - + + Chrono Guide + +
+
+ + + + {sidebar_items.map((item) => { + const Icon = item.icon; + const active = isActive(item.href); + + return ( + + {active && ( +
+ )} + + + + {item.label} + + + + ); + })} + - - - - Logout - + + + + + + Collapse + + + + + + Logout + + + ); diff --git a/frontend/src/components/jobs/JobList.tsx b/frontend/src/components/jobs/JobList.tsx index 437f27f..b3bbeed 100644 --- a/frontend/src/components/jobs/JobList.tsx +++ b/frontend/src/components/jobs/JobList.tsx @@ -45,7 +45,7 @@ export function JobList() { }; return ( -
+
diff --git a/frontend/src/components/schedule/ScheduleItemsList.tsx b/frontend/src/components/schedule/ScheduleItemsList.tsx index ff1d0ee..a97fb9b 100644 --- a/frontend/src/components/schedule/ScheduleItemsList.tsx +++ b/frontend/src/components/schedule/ScheduleItemsList.tsx @@ -73,7 +73,7 @@ export default function ScheduleItemsList() { if (isLoading) { return ( - + Schedule Loading schedule items... @@ -83,8 +83,8 @@ export default function ScheduleItemsList() { } return ( -
- +
+
diff --git a/frontend/src/components/tasks/DraftEditDialog.tsx b/frontend/src/components/tasks/DraftEditDialog.tsx index c5f0138..7ce0e51 100644 --- a/frontend/src/components/tasks/DraftEditDialog.tsx +++ b/frontend/src/components/tasks/DraftEditDialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -47,41 +47,41 @@ export function DraftEditDialog({ tasks, selectedIndices, trigger, - isSingleEdit = false, onUpdate, }: EditDialogProps) { - const [priority, setPriority] = useState(undefined); - const [date, setDate] = useState(undefined); - const [time, setTime] = useState("10:30"); - const [datePickerOpen, setDatePickerOpen] = useState(false); - const [dialogOpen, setDialogOpen] = useState(false); - - const selectedCount = selectedIndices.size; - - useEffect(() => { - if (isSingleEdit && selectedCount === 1 && dialogOpen) { + const isSingleEdit = selectedIndices.size === 1; + const getInitialPriority = () => { + if (isSingleEdit) { + const index = Array.from(selectedIndices)[0]; + return tasks[index]?.priority?.toString() || "2"; + } + return undefined; + }; + const getInitialDate = () => { + if (isSingleEdit) { const index = Array.from(selectedIndices)[0]; const task = tasks[index]; - if (task) { - setPriority(task.priority?.toString() || "2"); - if (task.deadline) { - const deadlineDate = new Date(task.deadline); - setDate(deadlineDate); - const hours = deadlineDate.getHours().toString().padStart(2, "0"); - const minutes = deadlineDate.getMinutes().toString().padStart(2, "0"); - setTime(`${hours}:${minutes}`); - } else { - setDate(undefined); - setTime("10:30"); - } - } - } else if (dialogOpen) { - setPriority(undefined); - setDate(undefined); - setTime("10:30"); + const deadline = task.deadline ? new Date(task.deadline) : undefined; + return deadline; } - }, [dialogOpen, isSingleEdit, selectedCount, selectedIndices, tasks]); + return undefined; + }; + const [dialogOpen, setDialogOpen] = useState(false); + const [priority, setPriority] = useState(() => + getInitialPriority() + ); + const [date, setDate] = useState(() => getInitialDate()); + const getInitalTime = () => { + if (isSingleEdit && date) { + const hours = date.getHours().toString().padStart(2, "0"); + const minutes = date.getMinutes().toString().padStart(2, "0"); + return `${hours}:${minutes}`; + } + return "10:30"; + }; + const [time, setTime] = useState(() => getInitalTime()); + const [datePickerOpen, setDatePickerOpen] = useState(false); const priorityLabels: Record = { "0": "Highest (0)", "1": "High (1)", @@ -105,7 +105,7 @@ export function DraftEditDialog({ const deadline = new Date(date); deadline.setHours(parseInt(hours, 10), parseInt(minutes, 10), 0, 0); updates.deadline = deadline.toISOString(); - } else if (isSingleEdit && date === undefined && selectedCount === 1) { + } else if (isSingleEdit && date === undefined) { const index = Array.from(selectedIndices)[0]; const task = tasks[index]; if (task?.deadline) { @@ -159,7 +159,7 @@ export function DraftEditDialog({ return ( {trigger && {trigger}} - + {isSingleEdit ? "Edit Task" : "Bulk Edit Tasks"} @@ -167,8 +167,8 @@ export function DraftEditDialog({ {isSingleEdit ? "Update priority and deadline for this task." - : `Update priority and deadline for ${selectedCount} selected${ - selectedCount === 1 ? " task" : " tasks" + : `Update priority and deadline for ${selectedIndices.size} selected${ + isSingleEdit ? " task" : " tasks" }. Leave fields empty to keep current values.`} diff --git a/frontend/src/components/tasks/IngestionInput.tsx b/frontend/src/components/tasks/IngestionInput.tsx index 1c0c2be..4c56a33 100644 --- a/frontend/src/components/tasks/IngestionInput.tsx +++ b/frontend/src/components/tasks/IngestionInput.tsx @@ -27,7 +27,12 @@ import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { toast } from "sonner"; -const ACCEPTED_FILE_TYPES = ["image/jpeg", "image/png", "application/pdf"]; +const ACCEPTED_FILE_TYPES = [ + "image/jpeg", + "image/png", + "application/pdf", + "text/plain", +]; const MAX_FILE_SIZE = 10 * 1024 * 1024; const fileSchema = z.object({ @@ -36,7 +41,7 @@ const fileSchema = z.object({ .refine((file) => file.size <= MAX_FILE_SIZE, "File must be less than 10MB") .refine( (file) => ACCEPTED_FILE_TYPES.includes(file.type), - "Only JPG, PNG, and PDF files are allowed" + "Only JPG, PNG, PDF, and TXT files are allowed" ), }); @@ -77,8 +82,8 @@ export default function IngestionInput() { } return ( -
- +
+ Create Tasks @@ -87,10 +92,11 @@ export default function IngestionInput() { Upload a file or paste text to extract tasks automatically - + setActiveTab(v as "file" | "text")} + className="flex-1 flex flex-col" > @@ -102,10 +108,10 @@ export default function IngestionInput() { Enter Text - + - + @@ -157,20 +163,20 @@ function FileInput({
( - + Input File - +
{selectedFile ? (
@@ -201,7 +207,7 @@ function FileInput({ Drag and drop a file here, or click to browse

- Supported: JPEG, PNG, PDF (max 10MB) + Supported: JPEG, PNG, PDF, TXT (max 10MB)

)} @@ -270,20 +276,20 @@ function TextInput({ ( - + Input Text - -
+ +