From b2bbc7f81bd036a1091754aaae5bf050553bed31 Mon Sep 17 00:00:00 2001 From: Aarushi Patidar <206544456+aarushi-patidar@users.noreply.github.com> Date: Sun, 31 Aug 2025 18:32:56 +0530 Subject: [PATCH 1/6] fix: move select and download buttons to catalogue --- src/components/CatalogueContent.tsx | 24 +++++++++++++++++++++ src/components/SideBar.tsx | 33 +++++------------------------ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/components/CatalogueContent.tsx b/src/components/CatalogueContent.tsx index c7acfe6..fc3e635 100644 --- a/src/components/CatalogueContent.tsx +++ b/src/components/CatalogueContent.tsx @@ -417,6 +417,30 @@ const CatalogueContent = () => { + + + {/* Select/Deselect/Download All Buttons */} +
+
+ Select All +
+
+ Deselect All +
+
+ Download Selected +
+
+ {relatedSubjects.length > 0 && (
diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index c9830ce..be8070a 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -18,11 +18,10 @@ function SideBar({ selectedSemesters, selectedAnswerKeyIncluded, filterOptions, - filtersNotPulled, handleApplyFilters, - handleSelectAll, - handleDeselectAll, - handleDownloadSelected: handleDownloadAll, + handleSelectAll, + handleDeselectAll, + handleDownloadSelected }: { loading: boolean; selectedExams: string[]; @@ -45,8 +44,8 @@ function SideBar({ semester: string[], anskey: boolean, ) => void; - handleSelectAll: () => void; - handleDeselectAll: () => void; + handleSelectAll: () => void; + handleDeselectAll: () => void; handleDownloadSelected: () => void; }) { const exams = @@ -170,28 +169,6 @@ function SideBar({
- {/* Select/Deselect/Download All Buttons */} -
-
- Select All -
-
- Deselect All -
-
- Download Selected -
-
- {/* Filters */} {filtersForSidebar.map((section) => (
Date: Sun, 31 Aug 2025 18:43:54 +0530 Subject: [PATCH 2/6] fix: move select and download buttons to catalogue --- src/components/SideBar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index be8070a..2fb3e05 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -18,6 +18,7 @@ function SideBar({ selectedSemesters, selectedAnswerKeyIncluded, filterOptions, + filtersNotPulled, handleApplyFilters, handleSelectAll, handleDeselectAll, From da8ac1b545c533dc74b6033c2b38809d87bcf175 Mon Sep 17 00:00:00 2001 From: Aarushi Patidar <206544456+aarushi-patidar@users.noreply.github.com> Date: Sun, 31 Aug 2025 19:44:09 +0530 Subject: [PATCH 3/6] Merge branch 'prod' into aarushi/catalogue --- README.md | 2 +- package.json | 6 + pnpm-lock.yaml | 315 +++++++++++++++ src/app/api/upload/route.ts | 2 +- src/app/upload/page.tsx | 638 +++++++++++++++++++++--------- src/components/Footer.tsx | 8 + src/components/SideBar.tsx | 115 ++---- src/components/SidebarButton.tsx | 23 ++ src/components/SidebarSection.tsx | 55 +++ src/components/screens/Faq.tsx | 12 +- src/db/papers.ts | 1 + src/interface.ts | 1 + src/styles/globals.css | 15 +- 13 files changed, 920 insertions(+), 273 deletions(-) create mode 100644 src/components/SidebarButton.tsx create mode 100644 src/components/SidebarSection.tsx diff --git a/README.md b/README.md index 50b5419..a73eac1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Codechef-VIT +

Codechef-VIT

.

Papers

diff --git a/package.json b/package.json index 8c4aaf9..2a80e9f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "start": "next start" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@google/genai": "^0.7.0", "@radix-ui/react-accordion": "^1.2.4", "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -51,6 +55,7 @@ "prettier-plugin-tailwindcss": "^0.6.11", "raw-loader": "^4.0.2", "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.8", "react-hot-toast": "^2.5.2", @@ -63,6 +68,7 @@ "devDependencies": { "@types/eslint": "8.56.12", "@types/react": "^18.3.20", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.3.6", "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3168b3b..191ef5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@18.3.1) + '@google/genai': + specifier: ^0.7.0 + version: 0.7.0 '@radix-ui/react-accordion': specifier: ^1.2.4 version: 1.2.4(@types/react-dom@18.3.6(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -131,6 +143,9 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 + react-beautiful-dnd: + specifier: ^13.1.1 + version: 13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) @@ -162,6 +177,9 @@ importers: '@types/react': specifier: ^18.3.20 version: 18.3.20 + '@types/react-beautiful-dnd': + specifier: ^13.1.8 + version: 13.1.8 '@types/react-dom': specifier: ^18.3.6 version: 18.3.6(@types/react@18.3.20) @@ -194,6 +212,28 @@ packages: resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -236,6 +276,10 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@google/genai@0.7.0': + resolution: {integrity: sha512-r+Fwj/emnXZN5R+4JCxDXboY4AGTmTn7+Wnori5dgyJiStP0P82f9YYL0CVsCnDIumNY2i0UIcZ1zGZdtHJ34w==} + engines: {node: '>=18.0.0'} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1096,6 +1140,11 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/hoist-non-react-statics@3.3.7': + resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} + peerDependencies: + '@types/react': '*' + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1112,11 +1161,17 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/react-beautiful-dnd@13.1.8': + resolution: {integrity: sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==} + '@types/react-dom@18.3.6': resolution: {integrity: sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==} peerDependencies: '@types/react': ^18.0.0 + '@types/react-redux@7.1.34': + resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==} + '@types/react@18.3.20': resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} @@ -1613,6 +1668,9 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2046,10 +2104,18 @@ packages: resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} engines: {node: '>=10'} + gaxios@6.7.1: + resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} + engines: {node: '>=14'} + gaxios@7.1.1: resolution: {integrity: sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==} engines: {node: '>=18'} + gcp-metadata@6.1.1: + resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} + engines: {node: '>=14'} + gcp-metadata@7.0.1: resolution: {integrity: sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==} engines: {node: '>=18'} @@ -2126,6 +2192,14 @@ packages: resolution: {integrity: sha512-HMxFl2NfeHYnaL1HoRIN1XgorKS+6CDaM+z9LSSN+i/nKDDL4KFFEWogMXu7jV4HZQy2MsxpY+wA5XIf3w410A==} engines: {node: '>=18'} + google-auth-library@9.15.1: + resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} + engines: {node: '>=14'} + + google-logging-utils@0.0.2: + resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + engines: {node: '>=14'} + google-logging-utils@1.1.1: resolution: {integrity: sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==} engines: {node: '>=14'} @@ -2148,6 +2222,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + gtoken@8.0.0: resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} engines: {node: '>=18'} @@ -2179,6 +2257,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -2302,6 +2383,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -2476,6 +2561,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} @@ -2631,6 +2719,15 @@ packages: engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2926,6 +3023,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -2939,6 +3039,13 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-beautiful-dnd@13.1.1: + resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==} + deprecated: 'react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672' + peerDependencies: + react: ^16.8.5 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -2965,6 +3072,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-pdf@9.2.1: resolution: {integrity: sha512-AJt0lAIkItWEZRA5d/mO+Om4nPCuTiQ0saA+qItO967DTjmGjnhmF+Bi2tL286mOTfBlF5CyLzJ35KTMaDoH+A==} peerDependencies: @@ -2975,6 +3085,18 @@ packages: '@types/react': optional: true + react-redux@7.2.9: + resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} + peerDependencies: + react: ^16.8.3 || ^17 || ^18 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -3033,6 +3155,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + redux@4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -3353,6 +3478,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@5.1.1: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} @@ -3439,6 +3567,11 @@ packages: '@types/react': optional: true + use-memo-one@1.1.3: + resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -3452,6 +3585,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -3463,6 +3600,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -3485,6 +3625,9 @@ packages: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3528,6 +3671,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -3559,6 +3714,31 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -3615,6 +3795,16 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@google/genai@0.7.0': + dependencies: + google-auth-library: 9.15.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -4429,6 +4619,11 @@ snapshots: '@types/estree@1.0.7': {} + '@types/hoist-non-react-statics@3.3.7(@types/react@18.3.20)': + dependencies: + '@types/react': 18.3.20 + hoist-non-react-statics: 3.3.2 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -4452,10 +4647,21 @@ snapshots: '@types/prop-types@15.7.14': {} + '@types/react-beautiful-dnd@13.1.8': + dependencies: + '@types/react': 18.3.20 + '@types/react-dom@18.3.6(@types/react@18.3.20)': dependencies: '@types/react': 18.3.20 + '@types/react-redux@7.1.34': + dependencies: + '@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.20) + '@types/react': 18.3.20 + hoist-non-react-statics: 3.3.2 + redux: 4.2.1 + '@types/react@18.3.20': dependencies: '@types/prop-types': 15.7.14 @@ -5003,6 +5209,10 @@ snapshots: crypto-js@4.2.0: {} + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.3.3 + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -5546,6 +5756,17 @@ snapshots: fuse.js@7.1.0: {} + gaxios@6.7.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + gaxios@7.1.1: dependencies: extend: 3.0.2 @@ -5554,6 +5775,15 @@ snapshots: transitivePeerDependencies: - supports-color + gcp-metadata@6.1.1: + dependencies: + gaxios: 6.7.1 + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + gcp-metadata@7.0.1: dependencies: gaxios: 7.1.1 @@ -5661,6 +5891,20 @@ snapshots: transitivePeerDependencies: - supports-color + google-auth-library@9.15.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.7.1 + gcp-metadata: 6.1.1 + gtoken: 7.1.0 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + google-logging-utils@0.0.2: {} + google-logging-utils@1.1.1: {} googleapis-common@8.0.0: @@ -5686,6 +5930,14 @@ snapshots: graphemer@1.4.0: {} + gtoken@7.1.0: + dependencies: + gaxios: 6.7.1 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + gtoken@8.0.0: dependencies: gaxios: 7.1.1 @@ -5715,6 +5967,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -5841,6 +6097,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -6016,6 +6274,8 @@ snapshots: math-intrinsics@1.1.0: {} + memoize-one@5.2.1: {} + memory-pager@1.5.0: {} merge-refs@1.3.0(@types/react@18.3.20): @@ -6150,6 +6410,10 @@ snapshots: node-domexception@1.0.0: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-fetch@3.3.2: dependencies: data-uri-to-buffer: 4.0.1 @@ -6388,6 +6652,8 @@ snapshots: queue-microtask@1.2.3: {} + raf-schd@4.0.3: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -6405,6 +6671,20 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-beautiful-dnd@13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.0 + css-box-model: 1.2.1 + memoize-one: 5.2.1 + raf-schd: 4.0.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + redux: 4.2.1 + use-memo-one: 1.1.3(react@18.3.1) + transitivePeerDependencies: + - react-native + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -6431,6 +6711,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-pdf@9.2.1(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 @@ -6446,6 +6728,18 @@ snapshots: optionalDependencies: '@types/react': 18.3.20 + react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.0 + '@types/react-redux': 7.1.34 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 17.0.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll-bar@2.3.8(@types/react@18.3.20)(react@18.3.1): dependencies: react: 18.3.1 @@ -6512,6 +6806,10 @@ snapshots: dependencies: picomatch: 2.3.1 + redux@4.2.1: + dependencies: + '@babel/runtime': 7.27.0 + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -6899,6 +7197,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + tr46@5.1.1: dependencies: punycode: 2.3.1 @@ -7012,6 +7312,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.20 + use-memo-one@1.1.3(react@18.3.1): + dependencies: + react: 18.3.1 + use-sidecar@1.1.3(@types/react@18.3.20)(react@18.3.1): dependencies: detect-node-es: 1.1.0 @@ -7022,6 +7326,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@9.0.1: {} + warning@4.0.3: dependencies: loose-envify: 1.4.0 @@ -7033,6 +7339,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} webpack-sources@3.2.3: {} @@ -7072,6 +7380,11 @@ snapshots: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -7141,6 +7454,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.3: {} + y18n@4.0.3: {} yaml@2.7.1: {} diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index dd36940..63ec9d9 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -92,7 +92,6 @@ export async function POST(req: Request) { ); } - const paper = new PaperAdmin({ cloudinary_index: configIndex, @@ -105,6 +104,7 @@ export async function POST(req: Request) { exam: null, semester: null, campus: null, + ambiguous_tags: [], }); await paper.save(); diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 0d35748..bf769b3 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -1,129 +1,260 @@ "use client"; -import React, { useState, useEffect } from "react"; -import axios, { AxiosError } from "axios"; + +import { useCallback, useEffect, useState } from "react"; import toast from "react-hot-toast"; -import { handleAPIError } from "../../util/error"; import { Button } from "@/components/ui/button"; - -import { type APIResponse } from "@/interface"; +import axios, { AxiosError } from "axios"; +import { FiTrash, FiPlus } from "react-icons/fi"; +import Image from "next/image"; +import { + DndContext, + closestCenter, + PointerSensor, + TouchSensor, + useSensor, + useSensors, + DragEndEvent, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + useSortable, + horizontalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; import Dropzone from "react-dropzone"; -import { Upload } from "lucide-react"; +import { Upload, XIcon } from "lucide-react"; + +interface APIResponse { + status: string; + message?: string; +} -const Page = () => { - const [campus, setCampus] = useState("Vellore"); +export default function Page() { + const campus = "Vellore"; const [files, setFiles] = useState([]); + const [previews, setPreviews] = useState< + { id: string; file: File; preview: string }[] + >([]); const [isUploading, setIsUploading] = useState(false); - const [, setResetSearch] = useState(false); - const [isDragging, setIsDragging] = useState(false); const [isGlobalDragging, setIsGlobalDragging] = useState(false); + const [zoomIndex, setZoomIndex] = useState(null); useEffect(() => { - const handleDragEnter = (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsGlobalDragging(true); + const onDragEnter = () => setIsGlobalDragging(true); + const onDragLeave = () => setIsGlobalDragging(false); + const onDrop = () => setIsGlobalDragging(false); + + document.addEventListener("dragenter", onDragEnter); + document.addEventListener("dragleave", onDragLeave); + document.addEventListener("drop", onDrop); + + return () => { + document.removeEventListener("dragenter", onDragEnter); + document.removeEventListener("dragleave", onDragLeave); + document.removeEventListener("drop", onDrop); }; + }, []); - const handleDragOver = (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); + // Cleanup URLs when component unmounts + useEffect(() => { + return () => { + previews.forEach((item) => { + try { + URL.revokeObjectURL(item.preview); + } catch (e) { + // Ignore errors + } + }); }; + }, []); // Only run on unmount - const handleDragLeave = (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); + const fileCheckAndSelect = useCallback( + (acceptedFiles: File[]) => { + const maxFileSize = 5 * 1024 * 1024; + const allowedFileTypes = [ + "application/pdf", + "image/jpeg", + "image/png", + "image/gif", + ]; - if ( - !e.relatedTarget || - (e.currentTarget !== e.relatedTarget && - !(e.currentTarget as Element)?.contains(e.relatedTarget as Node)) - ) { - setIsGlobalDragging(false); + const toastId = toast.loading("Adding your files..."); + if (!acceptedFiles || acceptedFiles.length === 0) { + toast.error("No files selected", { id: toastId }); + return; } - }; - const handleDrop = (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsGlobalDragging(false); - }; + const isNewPdf = acceptedFiles.some( + (file) => file.type === "application/pdf", + ); - document.addEventListener("dragenter", handleDragEnter); - document.addEventListener("dragover", handleDragOver); - document.addEventListener("dragleave", handleDragLeave); - document.addEventListener("drop", handleDrop); + const hasExistingImages = files.some((file) => + file.type.startsWith("image/"), + ); - return () => { - document.removeEventListener("dragenter", handleDragEnter); - document.removeEventListener("dragover", handleDragOver); - document.removeEventListener("dragleave", handleDragLeave); - document.removeEventListener("drop", handleDrop); - }; - }, []); + const hasExistingPdf = files.some( + (file) => file.type === "application/pdf", + ); - function fileCheckAndSelect(acceptedFiles: T[]) { - const maxFileSize = 5 * 1024 * 1024; - const allowedFileTypes = [ - "application/pdf", - "image/jpeg", - "image/png", - "image/gif", - ]; - - const toastId = toast.loading("uploading your files"); - if (!acceptedFiles || acceptedFiles.length === 0) { - toast.error("No files selected", { - id: toastId, - }); - return; - } + if (isNewPdf && acceptedFiles.length > 1) { + toast.error("Only one PDF can be uploaded at a time.", { + id: toastId, + }); + return; + } - if (acceptedFiles.length > 5) { - toast.error("More than 5 files selected", { - id: toastId, - }); - return; - } + if (isNewPdf && hasExistingImages) { + toast.error("PDFs cannot be uploaded together with images.", { + id: toastId, + }); + return; + } - const invalidFiles = acceptedFiles.filter( - (file) => - file.size > maxFileSize || !allowedFileTypes.includes(file.type), - ); - if (invalidFiles.length > 0) { - toast.error( - `Some files are invalid. Ensure each file is below 5MB and of an allowed type (PDF, JPEG, PNG, GIF).`, - { + if (isNewPdf && hasExistingPdf) { + toast.error("Only one PDF is allowed. You’ve already uploaded a PDF.", { id: toastId, - }, + }); + return; + } + + if (!isNewPdf && hasExistingPdf) { + toast.error( + "Images cannot be uploaded after a PDF. Upload them separately.", + { + id: toastId, + }, + ); + return; + } + + const allFiles = [...files, ...acceptedFiles]; + if (allFiles.length > 5) { + toast.error("You can upload up to 5 files only", { id: toastId }); + return; + } + + const invalidFiles = acceptedFiles.filter( + (file) => + file.size > maxFileSize || !allowedFileTypes.includes(file.type), ); - return; - } - const isPdf = acceptedFiles.reduce( - (reducer, file) => file.type === "application/pdf" || reducer, - false, + if (invalidFiles.length > 0) { + toast.error( + "Some files are invalid. Make sure each is under 5MB and of allowed types (PDF, JPEG, PNG, GIF).", + { id: toastId }, + ); + return; + } + + const newPreviews = acceptedFiles.map((file, idx) => ({ + id: `${file.name}-${file.lastModified}-${Date.now()}-${files.length + idx}`, + file, + preview: URL.createObjectURL(file), + })); + + setFiles((prev) => [...prev, ...acceptedFiles]); + setPreviews((prev) => [...prev, ...newPreviews]); + + toast.success(`${acceptedFiles.length} file(s) added!`, { id: toastId }); + }, + [files], + ); + + const onDrop = useCallback( + (acceptedFiles: File[]) => { + fileCheckAndSelect(acceptedFiles); + }, + [fileCheckAndSelect], + ); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { distance: 5 }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 200, + tolerance: 10, + }, + }), + ); + + function SortablePreview({ + id, + children, + }: { + id: string; + children: React.ReactNode; + }) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + zIndex: isDragging ? 1000 : 1, + touchAction: "none", // Prevent scrolling during touch drag + }; + + return ( +
+ {children} +
); - if (isPdf && acceptedFiles.length > 1) { - toast.error("PDFs must be uploaded separately", { - id: toastId, - }); - return; + } + + const handleDndKitDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (over && active.id !== over.id) { + const oldIndex = previews.findIndex((item) => item.id === active.id); + const newIndex = previews.findIndex((item) => item.id === over.id); + + const newPreviews = arrayMove(previews, oldIndex, newIndex); + const newFiles = arrayMove(files, oldIndex, newIndex); + + setFiles(newFiles); + setPreviews(newPreviews); } + }; - const orderedFiles = acceptedFiles.sort((a, b) => { - return a.lastModified - b.lastModified; - }); - setFiles(orderedFiles); - toast.success(`${orderedFiles.length} files selected!`, { - id: toastId, + const handleDelete = (index: number) => { + const deletedPreview = previews[index]; + if (deletedPreview) { + URL.revokeObjectURL(deletedPreview.preview); + } + + const remainingFiles = files.filter((_, i) => i !== index); + const remainingPreviews = previews.filter((_, i) => i !== index); + + setFiles(remainingFiles); + setPreviews(remainingPreviews); + }; + + const clearAllFiles = useCallback(() => { + previews.forEach((item) => { + try { + URL.revokeObjectURL(item.preview); + } catch (e) {} }); - } - const handlePrint = async () => { - if (!campus) { - setCampus("Vellore"); - } + setFiles([]); + setPreviews([]); + }, [previews]); + const handlePrint = async () => { const isPdf = files.length === 1 && files[0]?.type === "application/pdf"; const formData = new FormData(); @@ -146,7 +277,7 @@ const Page = () => { if (error instanceof AxiosError && error.response?.data) { const errorData = error.response.data as APIResponse; const errorMessage = - errorData.message || "Failed to upload papers"; + errorData.message ?? "Failed to upload papers"; throw new Error(errorMessage); } throw new Error("Failed to upload papers"); @@ -161,98 +292,249 @@ const Page = () => { }, ); - setFiles([]); - setResetSearch(true); - setTimeout(() => setResetSearch(false), 100); + clearAllFiles(); } catch (error) { - handleAPIError(error); } finally { setIsUploading(false); } }; - const isCurrentlyDragging = isDragging || isGlobalDragging; - return ( -
-
-
-
- {/* File Dropzone */} -
- setIsDragging(true)} - onDragLeave={() => setIsDragging(false)} - onDropAccepted={() => { - setIsDragging(false); - setIsGlobalDragging(false); - }} - onDropRejected={() => { - setIsDragging(false); - setIsGlobalDragging(false); - }} - > - {({ getRootProps, getInputProps }) => ( -
+
+
+ {previews.length === 0 && ( +
+
+
+ - - {isCurrentlyDragging ? ( -
-

- Drop files here -

- -
- ) : ( -
-

- Drag 'n' drop some files here, or{" "} - click to select - files -

-
-

- Note: Uploaded papers are first reviewed by our team before appearing on the website. If your paper doesn't show up immediately, please be patient,it's likely still under review. -

+ {({ getRootProps, getInputProps, isDragActive }) => ( +
+ + {isDragActive || isGlobalDragging ? ( +
+

+ Drop files here +

+ +
+ ) : ( +
+ Drag 'n' drop some files here, or{" "} + click to + select files +
+ )} +
+ {files.length} files selected
-
+
+ Note: Uploaded papers are first reviewed by our team + before appearing on the website. If your paper + doesn't show up immediately, please be patient, + it's likely still under review. +
+
)} -
+ +
+
+
+ )} + + {previews.length > 0 && ( + + {({ getRootProps, getInputProps, isDragActive }) => ( +
+ +
+
+
+
+ +
+
+ {previews.length} +
+
+ )} + + )} + {previews.length > 0 && ( +
+
+
+ + item.id)} + strategy={horizontalListSortingStrategy} > - {files?.length || 0} files selected -
-
- )} - - -
-
- - +
+ {previews.map((item, index) => ( + +
+
+ {/* Index badge */} +
+ + {index + 1} + +
+ + {/* Delete button */} + + + {/* Preview */} +
+ {item.file.type.startsWith("image/") ? ( + {`Page + ) : ( +