From 6d93244f94af361064c5d0033e3d939c1495b183 Mon Sep 17 00:00:00 2001 From: Aditya948351 Date: Wed, 8 Jul 2026 19:04:49 +0530 Subject: [PATCH] Revert recent 5 PR merges --- package-lock.json | 56 +- src/app/community/page.tsx | 110 +--- src/app/community/view/client.tsx | 5 +- src/app/opensource/page.tsx | 3 +- src/app/progress/page.tsx | 22 +- src/app/roadmaps/[id]/page.tsx | 37 -- src/app/u/client.tsx | 3 +- src/components/common/Pagination.tsx | 51 -- .../features/LearningPacePredictor.tsx | 506 ------------------ src/components/features/RoadmapChecklist.tsx | 334 ------------ .../features/SkillTreeVisualizer.tsx | 66 ++- .../__tests__/LearningPacePredictor.test.tsx | 95 ---- .../layout/__tests__/SearchModal.test.tsx | 51 +- src/components/profile/FollowButton.tsx | 3 +- src/components/profile/UserProfile.tsx | 18 +- src/components/projects/ProjectCard.tsx | 3 +- .../projects/ProjectCardSkeleton.tsx | 42 -- .../resources/InternshipCalendarModal.tsx | 3 +- src/hooks/useLearningProgress.ts | 154 ++---- src/lib/constants.ts | 24 - src/utils/__tests__/pacePredictor.test.ts | 97 ---- src/utils/pacePredictor.ts | 210 -------- 22 files changed, 192 insertions(+), 1701 deletions(-) delete mode 100644 src/components/common/Pagination.tsx delete mode 100644 src/components/features/LearningPacePredictor.tsx delete mode 100644 src/components/features/RoadmapChecklist.tsx delete mode 100644 src/components/features/__tests__/LearningPacePredictor.test.tsx delete mode 100644 src/components/projects/ProjectCardSkeleton.tsx delete mode 100644 src/lib/constants.ts delete mode 100644 src/utils/__tests__/pacePredictor.test.ts delete mode 100644 src/utils/pacePredictor.ts diff --git a/package-lock.json b/package-lock.json index 53fbb7d3..3536e7e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -159,6 +159,7 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -2152,6 +2153,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2175,6 +2177,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2519,6 +2522,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.12.tgz", "integrity": "sha512-FT+HoNp1NdaZ/N26hCwV3WbxS1m6gTn3p2QRBQ3KH7YqyCQqJx0iT7126RgVk68/Rq+9DeL/zCFnHZ0C4u1nLQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", @@ -2585,6 +2589,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.12.tgz", "integrity": "sha512-Pe513OBerK/CIBxz4/za9atd5MsZtd6DzHz4cmqkvkrcDWhQChAoHBpZ3McuZNuSP8YZiKwfX/J1frR07l15/w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/app": "0.14.12", "@firebase/component": "0.7.3", @@ -2601,6 +2606,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz", "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/logger": "0.5.1" } @@ -3054,6 +3060,7 @@ "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -4942,6 +4949,7 @@ "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.60.0" }, @@ -5526,6 +5534,7 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -5860,7 +5869,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5881,7 +5889,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -5895,7 +5902,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -5905,8 +5911,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@testing-library/dom/node_modules/pretty-format": { "version": "27.5.1", @@ -5914,7 +5919,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5929,8 +5933,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", @@ -6041,8 +6044,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6050,6 +6052,7 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -6316,6 +6319,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6356,6 +6360,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6366,6 +6371,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6456,6 +6462,7 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", "license": "MIT", + "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -6566,6 +6573,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -7302,6 +7310,7 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7992,6 +8001,7 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -8375,6 +8385,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -10464,6 +10475,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10649,6 +10661,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -15179,6 +15192,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -15238,6 +15252,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -16020,7 +16035,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -16094,6 +16108,7 @@ "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -17412,6 +17427,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.2.7.tgz", "integrity": "sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.2.7", "@swc/helpers": "0.5.15", @@ -18296,6 +18312,7 @@ "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -18531,6 +18548,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -18584,6 +18602,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -18743,6 +18762,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -19252,6 +19272,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19261,6 +19282,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -19842,6 +19864,7 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -20090,6 +20113,7 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21856,7 +21880,8 @@ "version": "0.182.0", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -21989,6 +22014,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -22172,6 +22198,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -22440,6 +22467,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23085,6 +23113,7 @@ "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -23546,6 +23575,7 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -23814,6 +23844,7 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -24225,6 +24256,7 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx index afe611ca..db3b06d7 100644 --- a/src/app/community/page.tsx +++ b/src/app/community/page.tsx @@ -1,6 +1,5 @@ 'use client'; -import { AUTH_MESSAGES } from '@/lib/constants'; import Fuse from 'fuse.js'; import ReviewsSection from './ReviewsSection'; import { useState, useEffect, useMemo } from 'react'; @@ -12,10 +11,7 @@ import { orderBy, getDocs, limit, - startAfter, collectionGroup, - type QueryDocumentSnapshot, - type DocumentData, } from 'firebase/firestore'; import { db } from '@/lib/firebase'; import { @@ -31,11 +27,8 @@ import { getEmbedUrl } from '@/lib/utils'; import { useRouter } from 'next/navigation'; import CreateDiscussionModal from '@/components/community/CreateDiscussionModal'; import ProjectCard from '@/components/projects/ProjectCard'; -import Pagination from '@/components/common/Pagination'; import DOMPurify from 'dompurify'; -const PROJECTS_PAGE_SIZE = 20; - export default function CommunityPage() { const { user } = useAuth(); const router = useRouter(); @@ -49,15 +42,8 @@ export default function CommunityPage() { const [showCreateModal, setShowCreateModal] = useState(false); const [selectedProject, setSelectedProject] = useState(null); const [searchQuery, setSearchQuery] = useState(''); - // Cursor-based pagination state for the Projects showcase - const [pageCursors, setPageCursors] = useState< - QueryDocumentSnapshot[] - >([]); - const [lastDocInPage, setLastDocInPage] = - useState | null>(null); - const [currentPage, setCurrentPage] = useState(1); - const [hasNextPage, setHasNextPage] = useState(false); - const fetchData = async (cursor?: QueryDocumentSnapshot) => { + + const fetchData = async () => { setLoading(true); try { if (activeTab === 'discussions') { @@ -72,27 +58,28 @@ export default function CommunityPage() { snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) ); } else { - // Fetch Projects for Showcase, one page at a time. - // We ask for PAGE_SIZE + 1 so we can tell whether a next page - // exists without firing a second query. - const orderField = sortOption === 'popular' ? 'starCount' : 'createdAt'; - const projectsRef = collection(db, 'projects'); - const constraints = cursor - ? [ - orderBy(orderField, 'desc'), - startAfter(cursor), - limit(PROJECTS_PAGE_SIZE + 1), - ] - : [orderBy(orderField, 'desc'), limit(PROJECTS_PAGE_SIZE + 1)]; + // Fetch Projects for Showcase + let q; + if (sortOption === 'popular') { + // Sort by starCount descending + q = query( + collection(db, 'projects'), + orderBy('starCount', 'desc'), + limit(20) + ); + } else { + // Sort by createdAt descending (default) + q = query( + collection(db, 'projects'), + orderBy('createdAt', 'desc'), + limit(20) + ); + } - const q = query(projectsRef, ...constraints); const snapshot = await getDocs(q); - const docs = snapshot.docs; - const pageDocs = docs.slice(0, PROJECTS_PAGE_SIZE); - - setProjects(pageDocs.map((doc) => ({ id: doc.id, ...doc.data() }))); - setHasNextPage(docs.length > PROJECTS_PAGE_SIZE); - setLastDocInPage(pageDocs[pageDocs.length - 1] ?? null); + setProjects( + snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) + ); } } catch (error: any) { console.error('Error fetching data:', error); @@ -106,29 +93,8 @@ export default function CommunityPage() { } }; - const handleNextPage = () => { - if (!hasNextPage || !lastDocInPage) return; - setPageCursors((prev) => [...prev, lastDocInPage]); - setCurrentPage((page) => page + 1); - fetchData(lastDocInPage); - }; - - const handlePreviousPage = () => { - if (pageCursors.length === 0) return; - const remainingCursors = pageCursors.slice(0, -1); - const previousCursor = remainingCursors[remainingCursors.length - 1]; - setPageCursors(remainingCursors); - setCurrentPage((page) => Math.max(1, page - 1)); - fetchData(previousCursor); - }; - useEffect(() => { - // Any time the tab or sort option changes, pagination resets to page 1. - setPageCursors([]); - setLastDocInPage(null); - setCurrentPage(1); fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab, sortOption]); const fuse = useMemo( @@ -171,7 +137,7 @@ export default function CommunityPage() { - - - Page {currentPage} - - - - - ); -} diff --git a/src/components/features/LearningPacePredictor.tsx b/src/components/features/LearningPacePredictor.tsx deleted file mode 100644 index e9b6ee0b..00000000 --- a/src/components/features/LearningPacePredictor.tsx +++ /dev/null @@ -1,506 +0,0 @@ -'use client'; - -import React, { useState, useEffect, useMemo } from 'react'; -import { useLearningProgress } from '@/hooks/useLearningProgress'; -import { useAuth } from '@/context/AuthContext'; -import { calculatePace, ROADMAPS_PACE_DATA, PacePredictorInput } from '@/utils/pacePredictor'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - Clock, - Calendar, - CheckCircle2, - AlertCircle, - Sparkles, - RefreshCw, - Compass, - Sliders, - Target -} from 'lucide-react'; -import Button from '../ui/Button'; - -export default function LearningPacePredictor() { - const { user } = useAuth(); - const { completedNodes, loading: progressLoading, isNodeCompleted } = useLearningProgress(); - - // Inputs state - const [roadmapId, setRoadmapId] = useState('frontend'); - const [studyHours, setStudyHours] = useState(2); - const [studyMinutes, setStudyMinutes] = useState(0); - const [useManualProgress, setUseManualProgress] = useState(false); - const [manualPercent, setManualPercent] = useState(30); - const [targetDate, setTargetDate] = useState(''); - - // Custom roadmap inputs - const [customTotal, setCustomTotal] = useState(6); - const [customHoursPerMilestone, setCustomHoursPerMilestone] = useState(20); - const [customCompletedCount, setCustomCompletedCount] = useState(2); - - // Manual completed checklist overrides - const [localCompletedNodes, setLocalCompletedNodes] = useState>({ - frontend: [], - backend: [], - }); - - // Sync Firebase completed nodes initially when progress finishes loading - useEffect(() => { - if (!progressLoading && user) { - const frontendCompleted = ['1', '2', '3', '4', '5'].filter(id => isNodeCompleted('Frontend', id)); - const backendCompleted = ['1', '2', '3'].filter(id => isNodeCompleted('Backend', id)); - - setLocalCompletedNodes(prev => { - const isFrontendSame = prev.frontend.length === frontendCompleted.length && - prev.frontend.every((v, i) => v === frontendCompleted[i]); - const isBackendSame = prev.backend.length === backendCompleted.length && - prev.backend.every((v, i) => v === backendCompleted[i]); - - if (isFrontendSame && isBackendSame) { - return prev; - } - return { - frontend: frontendCompleted, - backend: backendCompleted, - }; - }); - } - }, [progressLoading, user]); - - - // Handle roadmap selection change - const handleRoadmapChange = (e: React.ChangeEvent) => { - setRoadmapId(e.target.value); - }; - - // Toggle milestone completion locally - const toggleLocalMilestone = (id: string) => { - setLocalCompletedNodes(prev => { - const currentList = prev[roadmapId] || []; - const updatedList = currentList.includes(id) - ? currentList.filter(item => item !== id) - : [...currentList, id]; - return { ...prev, [roadmapId]: updatedList }; - }); - }; - - // Calculate study hours per day - const studyHoursPerDay = useMemo(() => { - const total = studyHours + studyMinutes / 60; - return isNaN(total) ? 0 : Math.max(0, total); - }, [studyHours, studyMinutes]); - - // Predictor calculations - const prediction = useMemo(() => { - const input: PacePredictorInput = { - roadmapId, - studyHoursPerDay, - useManualProgress, - manualProgressPercent: manualPercent, - completedMilestoneIds: localCompletedNodes[roadmapId] || [], - targetCompletionDate: targetDate || undefined, - customTotalMilestones: customTotal, - customTypicalHoursPerMilestone: customHoursPerMilestone, - customCompletedMilestonesCount: customCompletedCount, - }; - return calculatePace(input); - }, [ - roadmapId, - studyHoursPerDay, - useManualProgress, - manualPercent, - localCompletedNodes, - targetDate, - customTotal, - customHoursPerMilestone, - customCompletedCount, - ]); - - const activeRoadmapConfig = ROADMAPS_PACE_DATA[roadmapId]; - - // Helper to format date - const formatDateString = (dateStr: string | null) => { - if (!dateStr) return 'N/A'; - try { - const d = new Date(dateStr); - return d.toLocaleDateString(undefined, { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - }); - } catch { - return dateStr; - } - }; - - // Reset predictor inputs - const handleReset = () => { - setStudyHours(2); - setStudyMinutes(0); - setUseManualProgress(false); - setManualPercent(0); - setTargetDate(''); - setCustomTotal(6); - setCustomHoursPerMilestone(20); - setCustomCompletedCount(0); - - // Reset local checklist to user progress if logged in, else empty - if (user) { - const frontendCompleted = ['1', '2', '3', '4', '5'].filter(id => isNodeCompleted('Frontend', id)); - const backendCompleted = ['1', '2', '3'].filter(id => isNodeCompleted('Backend', id)); - setLocalCompletedNodes({ - frontend: frontendCompleted, - backend: backendCompleted, - }); - } else { - setLocalCompletedNodes({ - frontend: [], - backend: [], - }); - } - }; - - return ( -
-
-
-

- - Smart Learning Pace Predictor -

-

- Predict when you will finish your learning roadmap based on daily study time. -

-
- -
- -
- {/* Left Inputs Panel */} -
- {/* Roadmap Selection */} -
- - -
- - {/* Daily Study Time */} -
- Daily Study Time -
-
- setStudyHours(Math.max(0, Math.min(24, parseInt(e.target.value) || 0)))} - className="w-full bg-transparent text-white font-semibold text-lg outline-none text-center" - placeholder="0" - aria-label="Study hours per day" - /> - hours -
-
- setStudyMinutes(Math.max(0, Math.min(59, parseInt(e.target.value) || 0)))} - className="w-full bg-transparent text-white font-semibold text-lg outline-none text-center" - placeholder="0" - aria-label="Study minutes per day" - /> - mins -
-
-
- - {/* Optional Target Date */} -
- - setTargetDate(e.target.value)} - className="w-full bg-slate-900 border border-slate-800 text-white rounded-xl px-4 py-3 outline-none focus:border-cyan-500 transition-colors" - /> -
- - {/* Mode Toggle: Checklist vs Slider */} - {roadmapId !== 'custom' && ( -
-
- - Use Manual Percentage Slider -
- -
- )} - - {/* Dynamic Progress Input Section */} -
-

- - Track Progress -

- - {roadmapId === 'custom' ? ( - // Custom roadmap Inputs -
-
-
- - setCustomTotal(Math.max(1, parseInt(e.target.value) || 1))} - className="bg-slate-900 border border-slate-800 text-white text-sm rounded-lg p-2.5 outline-none" - /> -
-
- - setCustomHoursPerMilestone(Math.max(1, parseInt(e.target.value) || 1))} - className="bg-slate-900 border border-slate-800 text-white text-sm rounded-lg p-2.5 outline-none" - /> -
-
- -
- - setCustomCompletedCount(parseInt(e.target.value) || 0)} - className="w-full accent-cyan-500 cursor-pointer" - /> -
-
- ) : useManualProgress ? ( - // Manual Percentage Slider -
-
- Current Progress - {manualPercent}% -
- setManualPercent(parseInt(e.target.value) || 0)} - className="w-full accent-cyan-500 cursor-pointer" - aria-label="Progress percentage" - /> -
- ) : ( - // Milestone Checklist (derived from firebase nodes / local overrides) -
-

Check off the milestones you have completed:

- {activeRoadmapConfig?.milestones.map((m) => { - const isChecked = (localCompletedNodes[roadmapId] || []).includes(m.id); - return ( - - ); - })} -
- )} -
-
- - {/* Right Prediction Dashboard */} -
- {/* Main Predictor Hero Panel */} -
-
- -
- -
- - Estimated Completion Date - - - {studyHoursPerDay === 0 ? ( -
- -

- Please increase your study hours. At 0 hours/day, you will never finish! -

-
- ) : prediction.remainingHours === 0 ? ( -
-

- Completed! 🎉 -

-

- Congratulations! You have completed all milestones for this roadmap. -

-
- ) : ( -
-

- {formatDateString(prediction.estimatedCompletionDate)} -

-

- Targeting a finish in {prediction.estimatedDaysToComplete} days ({Math.ceil(prediction.estimatedDaysToComplete / 7)} weeks). -

-
- )} -
- - {/* Progress Micro-Bar */} -
-
- Roadmap Progress - {prediction.progressPercent}% -
-
-
-
-
-
- - {/* Calculation Metrics Breakdown Grid */} -
-
- REMAINING EFFORT - {prediction.remainingHours} - hours - Total: {prediction.totalRoadmapHours} hours -
-
- REMAINING MILESTONES - {prediction.remainingMilestonesCount} - / {prediction.totalMilestones} - Completed: {prediction.completedMilestonesCount} -
-
- - {/* Target Date Feedback and Recommendations */} - - {targetDate && studyHoursPerDay > 0 && prediction.remainingHours > 0 && ( - -
-
- {prediction.targetMeetsDeadline ? ( - - ) : ( - - )} -
-
-

- {prediction.targetMeetsDeadline - ? "You're on track! 🚀" - : 'Adjustments recommended to hit target date'} -

-

- {prediction.targetMeetsDeadline - ? `At your current pace of ${studyHoursPerDay} hrs/day, you will complete the roadmap around ${prediction.daysToTargetDate && prediction.estimatedDaysToComplete ? prediction.daysToTargetDate - prediction.estimatedDaysToComplete : 0} days ahead of your target date.` - : `To complete your roadmap by ${formatDateString(targetDate)}, you need to increase your daily study time to at least:`} -

- - {!prediction.targetMeetsDeadline && prediction.suggestedStudyHoursPerDay && ( -
- - {prediction.suggestedStudyHoursPerDay} - - hours / day - {prediction.suggestedStudyHoursPerDay > 10 && ( - - ⚠️ Intensive Pace - - )} -
- )} -
-
-
- )} -
-
-
-
- ); -} diff --git a/src/components/features/RoadmapChecklist.tsx b/src/components/features/RoadmapChecklist.tsx deleted file mode 100644 index 07c9898b..00000000 --- a/src/components/features/RoadmapChecklist.tsx +++ /dev/null @@ -1,334 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - CheckCircle2, - Circle, - ChevronDown, - ChevronUp, - RotateCcw, - Trophy, - Flame, - Zap, -} from 'lucide-react'; -import { useLearningProgress } from '@/hooks/useLearningProgress'; -import { useAuth } from '@/context/AuthContext'; - -// ── Types ────────────────────────────────────────────────────────────────── - -interface ChecklistNode { - id: string; - label: string; - desc: string; -} - -interface RoadmapChecklistProps { - /** The path identifier, e.g. "Frontend" or "Backend" */ - pathId: string; - /** Human-readable title for this roadmap, e.g. "Frontend Developer Roadmap" */ - title: string; - /** All nodes in this roadmap */ - nodes: ChecklistNode[]; -} - -// ── Helper: motivational label based on progress ────────────────────────── - -function getMotivationalLabel(percent: number): { text: string; emoji: string } { - if (percent === 0) return { text: 'Ready to begin your journey?', emoji: '🚀' }; - if (percent < 25) return { text: 'Great start! Keep going!', emoji: '🌱' }; - if (percent < 50) return { text: "You're building momentum!", emoji: '⚡' }; - if (percent < 75) return { text: 'More than halfway there!', emoji: '🔥' }; - if (percent < 100) return { text: 'Almost mastered! Push through!', emoji: '🏆' }; - return { text: 'Path mastered! Legendary!', emoji: '🎉' }; -} - -// ── Sub-component: individual checklist row ─────────────────────────────── - -function ChecklistRow({ - node, - pathId, - index, -}: { - node: ChecklistNode; - pathId: string; - index: number; -}) { - const { isNodeCompleted, toggleNode } = useLearningProgress(); - const isCompleted = isNodeCompleted(pathId, node.id); - const [isExpanded, setIsExpanded] = useState(false); - - return ( - -
- {/* Checkbox button */} - - - {/* Node step number badge */} - - {index + 1} - - - {/* Label */} - - {node.label} - - - {/* Expand toggle */} - -
- - {/* Expandable description */} - - {isExpanded && ( - -
- {node.desc} -
-
- )} -
- - {/* Completed shimmer accent */} - {isCompleted && ( - - )} - - ); -} - -// ── Main Component ───────────────────────────────────────────────────────── - -export default function RoadmapChecklist({ pathId, title, nodes }: RoadmapChecklistProps) { - const { user } = useAuth(); - const { isNodeCompleted, resetProgress } = useLearningProgress(); - const [showResetConfirm, setShowResetConfirm] = useState(false); - - const completedCount = nodes.filter((n) => isNodeCompleted(pathId, n.id)).length; - const totalCount = nodes.length; - const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0; - const { text: motivationalText, emoji } = getMotivationalLabel(progressPercent); - - const handleReset = async () => { - await resetProgress(pathId); - setShowResetConfirm(false); - }; - - return ( -
- {/* ── Header Card ── */} -
- {/* Decorative glow orbs */} -
-
- -
- {/* Title row */} -
-
-
- - - Checklist Tracker - -
-

{title}

-

- {user - ? 'Progress synced to your account in real-time.' - : 'Progress saved locally in your browser. Sign in to sync across devices.'} -

-
- - {/* Stats bubble */} -
-
- 0 ? 'text-yellow-400' : 'text-slate-600'} /> - {progressPercent}% -
- - {completedCount} / {totalCount} completed - -
-
- - {/* Progress bar */} -
-
- - {progressPercent > 10 && ( -
- )} - -
- - {/* Motivational label */} -
- - {emoji} {motivationalText} - - {progressPercent === 100 && ( - - - Path Mastered! - - )} -
-
- - {/* Reset button row */} -
- - {!showResetConfirm ? ( - setShowResetConfirm(true)} - disabled={completedCount === 0} - className="inline-flex items-center gap-1.5 text-xs text-slate-500 hover:text-red-400 disabled:opacity-30 disabled:cursor-not-allowed transition-colors duration-200 py-1 px-2 rounded-lg hover:bg-red-950/30" - > - - Reset Progress - - ) : ( - - Reset all progress for this path? - - - - )} - -
-
-
- - {/* ── Checklist Rows ── */} -
- {nodes.map((node, index) => ( - - ))} -
- - {/* ── Guest CTA ── */} - {!user && ( - -

- 🔒 Your progress is saved in this browser only.{' '} - - Sign in - {' '} - to sync your progress across all your devices. -

-
- )} -
- ); -} diff --git a/src/components/features/SkillTreeVisualizer.tsx b/src/components/features/SkillTreeVisualizer.tsx index 9155f336..376b38ae 100644 --- a/src/components/features/SkillTreeVisualizer.tsx +++ b/src/components/features/SkillTreeVisualizer.tsx @@ -139,6 +139,40 @@ export default function SkillTreeVisualizer({ }, }); + // Listen for the escape close-all-overlays event to close the side drawer + useEffect(() => { + const handleCloseAll = () => { + setSelectedNode(null); + }; + window.addEventListener('close-all-overlays', handleCloseAll); + return () => + window.removeEventListener('close-all-overlays', handleCloseAll); + }, []); + + // Bind local arrow key shortcuts for node selection cycling + useKeyboardShortcuts({ + arrowright: () => { + if (nodes.length === 0) return; + const currentIndex = selectedNode + ? nodes.findIndex((n) => n.id === selectedNode.id) + : -1; + const nextIndex = + currentIndex === -1 ? 0 : (currentIndex + 1) % nodes.length; + setSelectedNode(nodes[nextIndex]); + }, + arrowleft: () => { + if (nodes.length === 0) return; + const currentIndex = selectedNode + ? nodes.findIndex((n) => n.id === selectedNode.id) + : -1; + const prevIndex = + currentIndex === -1 + ? nodes.length - 1 + : (currentIndex - 1 + nodes.length) % nodes.length; + setSelectedNode(nodes[prevIndex]); + }, + }); + // Listen for the escape close-all-overlays event to close the side drawer useEffect(() => { const handleCloseAll = () => { @@ -210,7 +244,7 @@ export default function SkillTreeVisualizer({

{user ? 'Your learning progress is synced automatically to your account in real-time.' - : 'Progress saved locally in your browser. Sign in to sync across devices.'} + : 'Sign in to save and sync your learning progress.'}

@@ -314,20 +348,22 @@ export default function SkillTreeVisualizer({ onClick={() => setSelectedNode(node)} >
- + {user && ( + + )} {node.label} diff --git a/src/components/features/__tests__/LearningPacePredictor.test.tsx b/src/components/features/__tests__/LearningPacePredictor.test.tsx deleted file mode 100644 index d0f31e9c..00000000 --- a/src/components/features/__tests__/LearningPacePredictor.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import '@testing-library/jest-dom'; -import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import LearningPacePredictor from '../LearningPacePredictor'; - -// Mock auth hook -jest.mock('@/context/AuthContext', () => ({ - useAuth: () => ({ - user: { uid: 'user-123' }, - }), -})); - -// Mock learning progress hook -jest.mock('@/hooks/useLearningProgress', () => ({ - useLearningProgress: () => ({ - completedNodes: ['Frontend-1', 'Frontend-2'], - loading: false, - isNodeCompleted: (pathId: string, nodeId: string) => { - if (pathId === 'Frontend' && (nodeId === '1' || nodeId === '2')) { - return true; - } - return false; - }, - }), -})); - -// Mock framer motion with a Proxy to handle any motion element (e.g. motion.button, motion.div) -jest.mock('framer-motion', () => { - const React = require('react'); - const mockMotion = new Proxy( - {}, - { - get: (target, prop) => { - return React.forwardRef(({ children, ...props }: any, ref: any) => { - const Tag = prop as string; - return React.createElement(Tag, { ...props, ref }, children); - }); - }, - } - ); - return { - motion: mockMotion, - AnimatePresence: ({ children }: any) => <>{children}, - }; -}); - -describe('LearningPacePredictor Component', () => { - const originalDate = Date; - - beforeAll(() => { - const mockDate = new Date('2026-07-07T00:00:00.000Z'); - global.Date = class extends Date { - constructor(...args: any[]) { - if (args.length > 0) { - return new originalDate(...args as [any]); - } - return mockDate; - } - } as any; - }); - - afterAll(() => { - global.Date = originalDate; - }); - - it('renders the predictor title and initial state', () => { - render(); - expect(screen.getByText('Smart Learning Pace Predictor')).toBeInTheDocument(); - expect(screen.getByLabelText('Select Learning Roadmap')).toHaveValue('frontend'); - }); - - it('correctly calculates and displays the prediction on input change', () => { - render(); - - const hoursInput = screen.getByLabelText('Study hours per day'); - fireEvent.change(hoursInput, { target: { value: '4' } }); - - // Progress: HTML/CSS (30) + JS (50) completed = 80 completed out of 205. - // Remaining hours: 125. - // 125 / 4 = 31.25 -> 32 days - expect(screen.getByText(/Targeting a finish in/)).toHaveTextContent('32 days'); - }); - - it('handles optional target date recommendations', () => { - render(); - - const targetDateInput = screen.getByLabelText(/Target Completion Date/); - // Setting target date to 2026-08-26 (50 days out) - fireEvent.change(targetDateInput, { target: { value: '2026-08-26' } }); - - // With 2 hours/day study time, 125 remaining hours -> 63 days - // 63 days > 50 days target -> shows warning and suggestions - expect(screen.getByText('Adjustments recommended to hit target date')).toBeInTheDocument(); - }); -}); diff --git a/src/components/layout/__tests__/SearchModal.test.tsx b/src/components/layout/__tests__/SearchModal.test.tsx index bc7fb7e9..c7ffd71d 100644 --- a/src/components/layout/__tests__/SearchModal.test.tsx +++ b/src/components/layout/__tests__/SearchModal.test.tsx @@ -27,15 +27,10 @@ jest.mock('framer-motion', () => { beforeEach(() => { jest.clearAllMocks(); - jest.useFakeTimers(); const { useUIStore } = require('@/stores/ui-store'); useUIStore.setState({ isSearchOpen: false }); }); -afterEach(() => { - jest.useRealTimers(); -}); - describe('SearchModal', () => { it('does not render when closed', () => { const { container } = render(); @@ -44,9 +39,7 @@ describe('SearchModal', () => { it('renders and focuses input when opened via Ctrl+K', () => { render(); - act(() => { - fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); expect( screen.getByPlaceholderText(/Search wiki articles/) ).toBeInTheDocument(); @@ -54,9 +47,7 @@ describe('SearchModal', () => { it('renders and focuses input when opened via Cmd+K', () => { render(); - act(() => { - fireEvent.keyDown(window, { metaKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { metaKey: true, key: 'k' }); expect( screen.getByPlaceholderText(/Search wiki articles/) ).toBeInTheDocument(); @@ -64,16 +55,12 @@ describe('SearchModal', () => { it('closes when pressing Escape', () => { render(); - act(() => { - fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); expect( screen.getByPlaceholderText(/Search wiki articles/) ).toBeInTheDocument(); - act(() => { - fireEvent.keyDown(window, { key: 'Escape' }); - }); + fireEvent.keyDown(window, { key: 'Escape' }); expect( screen.queryByPlaceholderText(/Search wiki articles/) ).not.toBeInTheDocument(); @@ -81,14 +68,10 @@ describe('SearchModal', () => { it('closes when clicking backdrop', () => { const { container } = render(); - act(() => { - fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); const backdrop = container.querySelector('.backdrop'); if (backdrop) { - act(() => { - fireEvent.click(backdrop); - }); + fireEvent.click(backdrop); } expect( screen.queryByPlaceholderText(/Search wiki articles/) @@ -97,38 +80,26 @@ describe('SearchModal', () => { it('filters results based on query', () => { render(); - act(() => { - fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); const input = screen.getByPlaceholderText(/Search wiki articles/); - act(() => { - fireEvent.change(input, { target: { value: 'react' } }); - jest.advanceTimersByTime(300); // Advance debounce timer - }); + fireEvent.change(input, { target: { value: 'react' } }); expect(screen.getByText('Full Stack React Guide')).toBeInTheDocument(); }); it('navigates and closes modal when result is clicked', () => { render(); - act(() => { - fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); - }); + fireEvent.keyDown(window, { ctrlKey: true, key: 'k' }); const input = screen.getByPlaceholderText(/Search wiki articles/); - act(() => { - fireEvent.change(input, { target: { value: 'react' } }); - jest.advanceTimersByTime(300); // Advance debounce timer - }); + fireEvent.change(input, { target: { value: 'react' } }); const resultButton = screen .getByText('Full Stack React Guide') .closest('button'); if (resultButton) { - act(() => { - fireEvent.click(resultButton); - }); + fireEvent.click(resultButton); } expect(mockPush).toHaveBeenCalledWith('/wiki?article=react'); diff --git a/src/components/profile/FollowButton.tsx b/src/components/profile/FollowButton.tsx index f5255e51..b8d24910 100644 --- a/src/components/profile/FollowButton.tsx +++ b/src/components/profile/FollowButton.tsx @@ -1,6 +1,5 @@ 'use client'; -import { AUTH_MESSAGES } from '@/lib/constants'; import { useState, useEffect } from 'react'; import { useAuth } from '@/context/AuthContext'; import { UserPlus, UserCheck, Loader2 } from 'lucide-react'; @@ -32,7 +31,7 @@ export default function FollowButton({ const handleFollowToggle = async () => { if (!user) { - alert(AUTH_MESSAGES.LOGIN_TO_FOLLOW_USERS); + alert('Please login to follow users.'); return; } if (user.uid === targetUserId) return; diff --git a/src/components/profile/UserProfile.tsx b/src/components/profile/UserProfile.tsx index aff6baa1..4a3a9a47 100644 --- a/src/components/profile/UserProfile.tsx +++ b/src/components/profile/UserProfile.tsx @@ -1,5 +1,4 @@ 'use client'; -import { AUTH_MESSAGES } from '@/lib/constants'; const AVATAR_FALLBACK = process.env.NEXT_PUBLIC_AVATAR_FALLBACK_URL ?? 'https://ui-avatars.com/api'; import { useState, useEffect, useRef } from 'react'; @@ -40,7 +39,6 @@ import styles from './Profile.module.css'; import 'github-markdown-css/github-markdown.css'; import ProjectUploadModal from '@/components/projects/ProjectUploadModal'; import ProjectCard from '@/components/projects/ProjectCard'; -import ProjectCardSkeleton from '@/components/projects/ProjectCardSkeleton'; import Achievements from '@/components/profile/Achievements'; import Rewards from '@/components/profile/Rewards'; import DevCard from '@/components/profile/DevCard'; @@ -409,7 +407,7 @@ export default function UserProfile() { if (!user) { return (
-

{AUTH_MESSAGES.LOGIN_TO_VIEW_PROFILE}

+

Please login to view your profile.

); } @@ -973,7 +971,19 @@ export default function UserProfile() { aria-label="Loading projects" > {Array.from({ length: 4 }).map((_, i) => ( - +
+
+
+
+
+
+
+
+
+
))}
) : projects.length === 0 ? ( diff --git a/src/components/projects/ProjectCard.tsx b/src/components/projects/ProjectCard.tsx index 9636f44b..e2c48be4 100644 --- a/src/components/projects/ProjectCard.tsx +++ b/src/components/projects/ProjectCard.tsx @@ -1,6 +1,5 @@ 'use client'; -import { AUTH_MESSAGES } from '@/lib/constants'; import { useState, type ReactNode } from 'react'; import { Target, ExternalLink, Edit3, Star, Bookmark } from 'lucide-react'; import Image from 'next/image'; @@ -119,7 +118,7 @@ export default function ProjectCard({ const handleToggleStar = async (e: React.MouseEvent) => { e.stopPropagation(); if (!user) { - alert(AUTH_MESSAGES.LOGIN_TO_STAR_PROJECTS); + alert('Please login to star projects.'); return; } if (isStarring) return; diff --git a/src/components/projects/ProjectCardSkeleton.tsx b/src/components/projects/ProjectCardSkeleton.tsx deleted file mode 100644 index 91644341..00000000 --- a/src/components/projects/ProjectCardSkeleton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -export default function ProjectCardSkeleton() { - return ( -