From b3fb95bf37ddd706c0f1b07a2179e2f5bd41bc4c Mon Sep 17 00:00:00 2001 From: Larry La Date: Fri, 4 Apr 2025 23:14:19 -0700 Subject: [PATCH 1/6] fix: like/follow routes --- backend/projects/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/projects/urls.py b/backend/projects/urls.py index f31094d..3f27e09 100644 --- a/backend/projects/urls.py +++ b/backend/projects/urls.py @@ -5,6 +5,6 @@ path('', get_projects, name='get_projects'), # Fetch all projects path('create/', ProjectCRUDView.as_view(), name='project-create'), # Create project path('/', ProjectCRUDView.as_view(), name='project-detail'), # Read, Update, Delete project by ID - path('like/', toggle_like, name='toggle_like'), - path('follow/', toggle_follow, name='toggle_follow') + path('like/', toggle_like, name='toggle_like'), + path('follow/', toggle_follow, name='toggle_follow') ] From b02100a2b6e478836cfe1a7011b2a3fbdf2a7a25 Mon Sep 17 00:00:00 2001 From: Rebecca Smith <2145912+Rebeccals1@users.noreply.github.com> Date: Sat, 12 Apr 2025 10:22:09 -0700 Subject: [PATCH 2/6] feat: Rebeccals onboard (#43) * Updated Onboarding Page * More onboarding updates * Fixed onboard container being too small. Added link to home slideshow. * changed onboard so its public * chore: workflow double run fix * chore: double unit test run fix --------- Co-authored-by: Rebecca Smith <2145912+Rebeccals@users.noreply.github.com> Co-authored-by: Larry La --- .github/workflows/deployment.yml | 2 +- .github/workflows/django-tests.yml | 2 +- frontend/bitmatch/package-lock.json | 442 ++++++++++++++++++ frontend/bitmatch/package.json | 3 + frontend/bitmatch/src/App.jsx | 127 ++--- .../components/onboarding/CreateProfile.jsx | 126 +++++ .../src/components/onboarding/Interest.jsx | 88 ++++ .../src/components/onboarding/Location.jsx | 97 ++++ .../src/components/onboarding/Roles.jsx | 88 ++++ .../src/components/onboarding/Skills.jsx | 88 ++++ .../components/onboarding/StepIndicator.jsx | 20 + .../src/components/ui/ImageSlideshow.jsx | 35 +- frontend/bitmatch/src/components/ui/badge.jsx | 14 + .../bitmatch/src/components/ui/checkbox.jsx | 29 ++ frontend/bitmatch/src/components/ui/toast.jsx | 85 ++++ .../bitmatch/src/components/ui/toaster.jsx | 33 ++ frontend/bitmatch/src/hooks/use-toast.js | 155 ++++++ frontend/bitmatch/src/views/HomePage.jsx | 2 +- frontend/bitmatch/src/views/OnboardPage.jsx | 242 +++++++--- frontend/bitmatch/src/views/SignInPage.jsx | 2 +- 20 files changed, 1526 insertions(+), 154 deletions(-) create mode 100644 frontend/bitmatch/src/components/onboarding/CreateProfile.jsx create mode 100644 frontend/bitmatch/src/components/onboarding/Interest.jsx create mode 100644 frontend/bitmatch/src/components/onboarding/Location.jsx create mode 100644 frontend/bitmatch/src/components/onboarding/Roles.jsx create mode 100644 frontend/bitmatch/src/components/onboarding/Skills.jsx create mode 100644 frontend/bitmatch/src/components/onboarding/StepIndicator.jsx create mode 100644 frontend/bitmatch/src/components/ui/badge.jsx create mode 100644 frontend/bitmatch/src/components/ui/checkbox.jsx create mode 100644 frontend/bitmatch/src/components/ui/toast.jsx create mode 100644 frontend/bitmatch/src/components/ui/toaster.jsx create mode 100644 frontend/bitmatch/src/hooks/use-toast.js diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index b587b23..742c666 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -6,7 +6,7 @@ on: - "*" pull_request: branches: - - "*" + - main jobs: terraform: diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index 0eebed1..43c15a9 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -6,7 +6,7 @@ on: - "*" pull_request: branches: - - "*" + - main workflow_dispatch: jobs: diff --git a/frontend/bitmatch/package-lock.json b/frontend/bitmatch/package-lock.json index 1b9f647..bc61c46 100644 --- a/frontend/bitmatch/package-lock.json +++ b/frontend/bitmatch/package-lock.json @@ -9,11 +9,14 @@ "version": "0.0.0", "dependencies": { "@clerk/clerk-react": "^5.24.0", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-scroll-area": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-toast": "^1.2.7", "@radix-ui/react-toggle": "^1.1.2", "axios": "^1.8.3", "class-variance-authority": "^0.7.1", @@ -1168,6 +1171,62 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz", + "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", @@ -1527,6 +1586,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz", + "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", @@ -1544,6 +1646,307 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.7.tgz", + "integrity": "sha512-0IWTbAUKvzdpOaWDMZisXZvScXzF0phaQjWspK8RUMEUxjLbli+886mB/kXTIC3F+t5vQ0n0vYn+dsX8s+WdfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.6", + "@radix-ui/react-portal": "1.1.5", + "@radix-ui/react-presence": "1.1.3", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.3.tgz", + "integrity": "sha512-mM2pxoQw5HJ49rkzwOs7Y6J4oYH22wS8BfK2/bBxROlI4xuR0c4jEenQP63LlTlDkO6Buj2Vt+QYAYcOgqtrXA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.6.tgz", + "integrity": "sha512-7gpgMT2gyKym9Jz2ZhlRXSg2y6cNQIK8d/cqBZ0RBCaps8pFryCWXiUKI+uHGFrhMrbGUP7U6PWgiXzIxoyF3Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.5.tgz", + "integrity": "sha512-ps/67ZqsFm+Mb6lSPJpfhRLrVL2i2fntgCmGMqqth4eaGUf+knAuuRtWVJrNjUhExgmdRqftSgzpf0DF0n6yXA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.3.tgz", + "integrity": "sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz", + "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.1.tgz", + "integrity": "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.3.tgz", + "integrity": "sha512-oXSF3ZQRd5fvomd9hmUCb2EHSZbPp3ZSHAHJJU/DlF9XoFkJBBW8RHU/E8WEH+RbSfJd/QFA0sl8ClJXknBwHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz", @@ -1635,6 +2038,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", @@ -1671,6 +2089,29 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", @@ -2539,6 +2980,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1" }, diff --git a/frontend/bitmatch/package.json b/frontend/bitmatch/package.json index 5d62722..734b044 100644 --- a/frontend/bitmatch/package.json +++ b/frontend/bitmatch/package.json @@ -11,11 +11,14 @@ }, "dependencies": { "@clerk/clerk-react": "^5.24.0", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-scroll-area": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-toast": "^1.2.7", "@radix-ui/react-toggle": "^1.1.2", "axios": "^1.8.3", "class-variance-authority": "^0.7.1", diff --git a/frontend/bitmatch/src/App.jsx b/frontend/bitmatch/src/App.jsx index 1dbb200..4431275 100644 --- a/frontend/bitmatch/src/App.jsx +++ b/frontend/bitmatch/src/App.jsx @@ -16,86 +16,91 @@ import ProjectDetailPage from "./views/IndividualProjectPage"; import AddProjectPage from "./views/AddProjectPage"; import SignUpPage from "./views/SignUpPage"; import SignInPage from "./views/SignInPage"; + import OnboardPage from "./views/OnboardPage"; -// import BrowsePage from "./views/BrowsePage"; -// import AboutPage from "./views/AboutPage"; +import InterestPage from './components/onboarding/Interest'; +import LocationPage from './components/onboarding/Location'; +import PositionPage from './components/onboarding/Roles'; +import SkillsPage from './components/onboarding/Skills'; +import UserPage from './components/onboarding/CreateProfile'; import "./styles/global.css"; +// AppRoutes is separated for access to hooks function AppRoutes() { const location = useLocation(); const { isSignedIn } = useUser(); - const isLanding = location.pathname === "/"; - const layoutClass = - isLanding && !isSignedIn - ? "py-8" - : "container mx-auto px-4 py-16 flex pb-6 flex-col items-center justify-center min-h-screen"; + + const pathname = location.pathname; + const isLanding = pathname === "/" && !isSignedIn; + const isOnboard = pathname.startsWith("/onboard"); + + const shouldUseContainer = !isLanding && !isOnboard; + + const layoutClass = shouldUseContainer + ? "container mx-auto px-4 py-16 pb-6 min-h-screen" + : ""; return ( -
- - {/* Landing or Home */} - - - - - - - - - } - /> + <> + - {/* Signed-in only routes */} - - - - } - /> - - - - } - /> - - - - } - /> +
+ + {/* Landing or Home */} + + + + + + + + + } + /> - {/* Public pages */} - } /> - } /> - } /> + {/* Signed-in only routes */} + } + /> + } + /> + } + /> - {/* Uncomment when ready */} - {/* - } /> - } /> - */} - -
+ {/* Public pages */} + } /> + } /> + + {/* Onboarding */} + }> + } /> + } /> + } /> + } /> + } /> + + + + + + ); } +// Default export so main.jsx works export default function App() { return ( - - ); } diff --git a/frontend/bitmatch/src/components/onboarding/CreateProfile.jsx b/frontend/bitmatch/src/components/onboarding/CreateProfile.jsx new file mode 100644 index 0000000..1e059f1 --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/CreateProfile.jsx @@ -0,0 +1,126 @@ +'use client' + +import { + forwardRef, + useEffect, + useState, + useImperativeHandle, +} from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Badge } from '@/components/ui/badge' +import { X } from 'lucide-react' + +// βœ… Component with forwardRef +const CreateProfile = forwardRef(({ onDataChange, formData }, ref) => { + const [firstName, setFirstName] = useState(formData?.first_name || '') + const [lastName, setLastName] = useState(formData?.last_name || '') + const [colleges, setColleges] = useState(formData?.colleges || []) + const [newCollege, setNewCollege] = useState('') + const [errors, setErrors] = useState({}) + + // Send data to parent on change + useEffect(() => { + onDataChange({ first_name: firstName, last_name: lastName, colleges }) + }, [firstName, lastName, colleges]) + + // Validation logic exposed via ref + useImperativeHandle(ref, () => ({ + validate: () => { + const newErrors = {} + if (!firstName.trim()) newErrors.firstName = 'First name is required.' + if (!lastName.trim()) newErrors.lastName = 'Last name is required.' + if (colleges.length === 0) newErrors.colleges = 'At least one college is required.' + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + }, + })) + + const handleAddCollege = () => { + const trimmed = newCollege.trim() + if (trimmed && !colleges.includes(trimmed)) { + const updated = [...colleges, trimmed].sort() + setColleges(updated) + setNewCollege('') + } + } + + const handleRemoveCollege = (college) => { + const updated = colleges.filter((c) => c !== college).sort() + setColleges(updated) + } + + return ( +
+ {/* First Name */} +
+ + setFirstName(e.target.value)} + placeholder="Enter your first name" + /> + {errors.firstName &&

{errors.firstName}

} +
+ + {/* Last Name */} +
+ + setLastName(e.target.value)} + placeholder="Enter your last name" + /> + {errors.lastName &&

{errors.lastName}

} +
+ + {/* Colleges */} +
+ +
+ setNewCollege(e.target.value)} + placeholder="Add college or university" + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + handleAddCollege() + } + }} + /> + +
+ +
+ {colleges.map((college) => ( + + {college} + + + ))} +
+ + {errors.colleges &&

{errors.colleges}

} +
+
+ ) +}) + +// βœ… Export wrapped with forwardRef +export default CreateProfile diff --git a/frontend/bitmatch/src/components/onboarding/Interest.jsx b/frontend/bitmatch/src/components/onboarding/Interest.jsx new file mode 100644 index 0000000..a1602c1 --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/Interest.jsx @@ -0,0 +1,88 @@ +'use client' + +import { + forwardRef, + useEffect, + useState, + useImperativeHandle, +} from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { X } from 'lucide-react' + +const Interest = forwardRef(({ onDataChange, formData }, ref) => { + const [interests, setInterests] = useState(formData?.interests || []) + const [newInterest, setNewInterest] = useState('') + const [error, setError] = useState('') + + useEffect(() => { + onDataChange({ interests }) + }, [interests]) + + const validate = () => { + if (interests.length === 0) { + setError('Please add at least one interest.') + return false + } + setError('') + return true + } + + useImperativeHandle(ref, () => ({ validate })) + + const handleAdd = () => { + const trimmed = newInterest.trim() + if (trimmed && !interests.includes(trimmed)) { + setInterests([...interests, trimmed].sort()) + setNewInterest('') + setError('') + } + } + + const handleRemove = (interest) => { + setInterests(interests.filter((i) => i !== interest)) + } + + return ( +
+ + +
+ setNewInterest(e.target.value)} + placeholder="Type a category" + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + handleAdd() + } + }} + /> + +
+ + {error &&

{error}

} + +
+ {interests.map((interest) => ( +
+ {interest} + +
+ ))} +
+
+ ) +}) + +export default Interest diff --git a/frontend/bitmatch/src/components/onboarding/Location.jsx b/frontend/bitmatch/src/components/onboarding/Location.jsx new file mode 100644 index 0000000..d99c234 --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/Location.jsx @@ -0,0 +1,97 @@ +'use client' + +import { + forwardRef, + useEffect, + useState, + useImperativeHandle, +} from 'react' +import { Checkbox } from '@/components/ui/checkbox' + +const Location = forwardRef(({ onDataChange, formData }, ref) => { + const [location, setLocation] = useState(formData?.location || '') + const [preferences, setPreferences] = useState( + formData?.location_preferences?.filter(p => p !== '') || ["Remote friendly projects"] + ) + const [other, setOther] = useState('') + const [errors, setErrors] = useState({}) + + const options = [ + "Near my location", + "Near my University or College location", + "Remote friendly projects", + ] + + useEffect(() => { + const allPreferences = [...preferences, ...(other ? [other] : [])] + onDataChange({ location, location_preferences: allPreferences }) + }, [location, preferences, other]) + + const validate = () => { + const newErrors = {} + if (!location.trim()) newErrors.location = 'Location is required.' + if (preferences.length === 0 && !other.trim()) { + newErrors.preferences = 'Please select or enter at least one preference.' + } + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + useImperativeHandle(ref, () => ({ validate })) + + const togglePreference = (value) => { + setPreferences((prev) => + prev.includes(value) + ? prev.filter((p) => p !== value) + : [...prev, value] + ) + } + + return ( +
+
+ + setLocation(e.target.value)} + placeholder="Enter your location" + /> + {errors.location &&

{errors.location}

} +
+ +
+ + + {options.map((option) => ( +
+ togglePreference(option)} + /> + +
+ ))} + +
+ + setOther(e.target.value)} + placeholder="Add custom preference" + /> +
+ + {errors.preferences &&

{errors.preferences}

} +
+
+ ) +}) + +export default Location diff --git a/frontend/bitmatch/src/components/onboarding/Roles.jsx b/frontend/bitmatch/src/components/onboarding/Roles.jsx new file mode 100644 index 0000000..2a88cd5 --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/Roles.jsx @@ -0,0 +1,88 @@ +'use client' + +import { + forwardRef, + useEffect, + useState, + useImperativeHandle, +} from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { X } from 'lucide-react' + +const Roles = forwardRef(({ onDataChange, formData }, ref) => { + const [roles, setRoles] = useState(formData?.roles || []) + const [newRole, setNewRole] = useState('') + const [error, setError] = useState('') + + useEffect(() => { + onDataChange({ roles }) + }, [roles]) + + const validate = () => { + if (roles.length === 0) { + setError('Please add at least one role.') + return false + } + setError('') + return true + } + + useImperativeHandle(ref, () => ({ validate })) + + const handleAdd = () => { + const trimmed = newRole.trim() + if (trimmed && !roles.includes(trimmed)) { + setRoles([...roles, trimmed].sort()) + setNewRole('') + setError('') + } + } + + const handleRemove = (role) => { + setRoles(roles.filter((r) => r !== role)) + } + + return ( +
+ + +
+ setNewRole(e.target.value)} + placeholder="Type a position" + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + handleAdd() + } + }} + /> + +
+ + {error &&

{error}

} + +
+ {roles.map((role) => ( +
+ {role} + +
+ ))} +
+
+ ) +}) + +export default Roles diff --git a/frontend/bitmatch/src/components/onboarding/Skills.jsx b/frontend/bitmatch/src/components/onboarding/Skills.jsx new file mode 100644 index 0000000..cfce8cf --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/Skills.jsx @@ -0,0 +1,88 @@ +'use client' + +import { + forwardRef, + useEffect, + useState, + useImperativeHandle, +} from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { X } from 'lucide-react' + +const Skills = forwardRef(({ onDataChange, formData }, ref) => { + const [skills, setSkills] = useState(formData?.skills || []) + const [newSkill, setNewSkill] = useState('') + const [error, setError] = useState('') + + useEffect(() => { + onDataChange({ skills }) + }, [skills]) + + const validate = () => { + if (skills.length === 0) { + setError('Please add at least one skill.') + return false + } + setError('') + return true + } + + useImperativeHandle(ref, () => ({ validate })) + + const handleAdd = () => { + const trimmed = newSkill.trim() + if (trimmed && !skills.includes(trimmed)) { + setSkills([...skills, trimmed].sort()) + setNewSkill('') + setError('') + } + } + + const handleRemove = (skill) => { + setSkills(skills.filter((s) => s !== skill)) + } + + return ( +
+ + +
+ setNewSkill(e.target.value)} + placeholder="Type a skill" + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + handleAdd() + } + }} + /> + +
+ + {error &&

{error}

} + +
+ {skills.map((skill) => ( +
+ {skill} + +
+ ))} +
+
+ ) +}) + +export default Skills diff --git a/frontend/bitmatch/src/components/onboarding/StepIndicator.jsx b/frontend/bitmatch/src/components/onboarding/StepIndicator.jsx new file mode 100644 index 0000000..2301757 --- /dev/null +++ b/frontend/bitmatch/src/components/onboarding/StepIndicator.jsx @@ -0,0 +1,20 @@ +export default function StepIndicator({ currentStep, totalSteps }) { + return ( +
+ {Array.from({ length: totalSteps }).map((_, index) => { + const isActive = index + 1 === currentStep + return ( +
+ ) + })} +
+ ) +} diff --git a/frontend/bitmatch/src/components/ui/ImageSlideshow.jsx b/frontend/bitmatch/src/components/ui/ImageSlideshow.jsx index 4b972b5..e747b76 100644 --- a/frontend/bitmatch/src/components/ui/ImageSlideshow.jsx +++ b/frontend/bitmatch/src/components/ui/ImageSlideshow.jsx @@ -67,26 +67,25 @@ export default function ImageSlideshow({ items }) { }} > {/* Darker Overlay */} -
-
-

- {slide.title} -

-

{slide.description}

- +

+ {slide.title} +

+

{slide.description}

+ +
-
+ ))} diff --git a/frontend/bitmatch/src/components/ui/badge.jsx b/frontend/bitmatch/src/components/ui/badge.jsx new file mode 100644 index 0000000..f7fe6f2 --- /dev/null +++ b/frontend/bitmatch/src/components/ui/badge.jsx @@ -0,0 +1,14 @@ +import React from "react" +import { cn } from "@/lib/utils" + +export function Badge({ className, ...props }) { + return ( +
+ ) +} diff --git a/frontend/bitmatch/src/components/ui/checkbox.jsx b/frontend/bitmatch/src/components/ui/checkbox.jsx new file mode 100644 index 0000000..b52b792 --- /dev/null +++ b/frontend/bitmatch/src/components/ui/checkbox.jsx @@ -0,0 +1,29 @@ +'use client' + +import React, { forwardRef } from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" +import { cn } from "@/lib/utils" + +const Checkbox = forwardRef((props, ref) => { + const { className, ...rest } = props + + return ( + + + + + + ) +}) + +Checkbox.displayName = "Checkbox" + +export { Checkbox } diff --git a/frontend/bitmatch/src/components/ui/toast.jsx b/frontend/bitmatch/src/components/ui/toast.jsx new file mode 100644 index 0000000..f8936f1 --- /dev/null +++ b/frontend/bitmatch/src/components/ui/toast.jsx @@ -0,0 +1,85 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva } from "class-variance-authority"; +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef(({ className, variant, ...props }, ref) => { + return ( + + ); +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction }; diff --git a/frontend/bitmatch/src/components/ui/toaster.jsx b/frontend/bitmatch/src/components/ui/toaster.jsx new file mode 100644 index 0000000..892a53d --- /dev/null +++ b/frontend/bitmatch/src/components/ui/toaster.jsx @@ -0,0 +1,33 @@ +import { useToast } from "@/hooks/use-toast" +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ); + })} + +
+ ); +} diff --git a/frontend/bitmatch/src/hooks/use-toast.js b/frontend/bitmatch/src/hooks/use-toast.js new file mode 100644 index 0000000..03accc0 --- /dev/null +++ b/frontend/bitmatch/src/hooks/use-toast.js @@ -0,0 +1,155 @@ +"use client"; +// Inspired by react-hot-toast library +import * as React from "react" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST" +} + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString(); +} + +const toastTimeouts = new Map() + +const addToRemoveQueue = (toastId) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state, action) => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t), + }; + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +} + +const listeners = [] + +let memoryState = { toasts: [] } + +function dispatch(action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +function toast({ + ...props +}) { + const id = genId() + + const update = (props) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + }; + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId }), + }; +} + +export { useToast, toast } diff --git a/frontend/bitmatch/src/views/HomePage.jsx b/frontend/bitmatch/src/views/HomePage.jsx index eb694c4..efb0bc0 100644 --- a/frontend/bitmatch/src/views/HomePage.jsx +++ b/frontend/bitmatch/src/views/HomePage.jsx @@ -56,7 +56,7 @@ export default function Home() { } return ( -
+
diff --git a/frontend/bitmatch/src/views/OnboardPage.jsx b/frontend/bitmatch/src/views/OnboardPage.jsx index ca532fd..e0c71f3 100644 --- a/frontend/bitmatch/src/views/OnboardPage.jsx +++ b/frontend/bitmatch/src/views/OnboardPage.jsx @@ -1,82 +1,182 @@ -import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/Label"; -import { Textarea } from "@/components/ui/Textarea"; +'use client' + +import { useState, useRef } from 'react' +import { ChevronLeft, ChevronRight } from 'lucide-react' +import { motion, AnimatePresence } from 'framer-motion' +import { Button } from '@/components/ui/button' +import { Toaster } from '@/components/ui/toaster' +import CreateProfile from '@/components/onboarding/CreateProfile' +import Location from '@/components/onboarding/Location' +import Roles from '@/components/onboarding/Roles' +import Interest from '@/components/onboarding/Interest' +import Skills from '@/components/onboarding/Skills' +import StepIndicator from '@/components/onboarding/StepIndicator' export default function OnboardPage() { - const navigate = useNavigate(); - const [formData, setFormData] = useState({ - role: "", - interests: "", - bio: "", - }); + const [currentStep, setCurrentStep] = useState(1) + const [formData, setFormData] = useState({}) + const totalSteps = 5 + + const createProfileRef = useRef() + const locationRef = useRef() + const rolesRef = useRef() + const interestRef = useRef() + const skillsRef = useRef() + + const stepTitles = [ + 'Create your Profile', + 'Location', + 'Roles & Positions', + 'Project Interests', + 'List Skill Sets', + ] + + const updateStepData = (data) => { + setFormData((prev) => ({ ...prev, ...data })) + } + + const validateStep = () => { + switch (currentStep) { + case 1: + return createProfileRef.current?.validate() ?? false + case 2: + return locationRef.current?.validate() ?? false + case 3: + return rolesRef.current?.validate() ?? false + case 4: + return interestRef.current?.validate() ?? false + case 5: + return skillsRef.current?.validate() ?? false + default: + return true + } + } - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ ...prev, [name]: value })); - }; + const handleNext = () => { + if (validateStep()) { + setCurrentStep(currentStep + 1) + } else { + toast({ + variant: 'destructive', + title: '🚫 Incomplete Step', + description: 'Please complete this step before proceeding.', + }) + } + } - const handleSubmit = (e) => { - e.preventDefault(); + const handlePrevious = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1) + } + } - // You can send this data to Supabase, Clerk user metadata, or your backend here - console.log("Onboarding data submitted:", formData); + const handleSubmit = async () => { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/user-profiles/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }) - // Redirect to dashboard or home - navigate("/dashboard"); - }; + if (!res.ok) throw new Error('Failed to submit') + alert('βœ… Profile submitted successfully!') + } catch (err) { + console.error(err) + alert('❌ Submission failed') + } + } + + const renderStep = () => { + const stepProps = { onDataChange: updateStepData, formData } + + switch (currentStep) { + case 1: + return + case 2: + return + case 3: + return + case 4: + return + case 5: + return + default: + return null + } + } return ( -
-
-

Work in Progress!

-

- This is just a placeholder for the onboarding process. Nothing works - right now. -

- -
-
- - -
- -
- - -
- -
- -