diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index 293c02a..0eebed1 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -19,6 +19,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_STORAGE_BUCKET_NAME: ${{ secrets.AWS_STORAGE_BUCKET_NAME }} + DJANGO_SECRET_KEY: ${{ secrets.DJANGO_SECRET_KEY }} steps: - name: Checkout code diff --git a/backend/bitmatch/settings.py b/backend/bitmatch/settings.py index 2f5345b..8c29feb 100644 --- a/backend/bitmatch/settings.py +++ b/backend/bitmatch/settings.py @@ -25,12 +25,17 @@ # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-b4wn$=0zp6j)8%uejmc)hp&3$j3myn$(&i_69(c@shw1i285*v' +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DJANGO_ENV = os.getenv("DJANGO_ENV", "DEV").upper() -ALLOWED_HOSTS = ["*"] +DEBUG = DJANGO_ENV == "DEV" + +if DJANGO_ENV == "PROD": + ALLOWED_HOSTS = ["bitmatchapp.com", "www.bitmatchapp.com"] +else: + ALLOWED_HOSTS = ["localhost", "127.0.0.1"] REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( @@ -178,6 +183,7 @@ "http://localhost:5173", "http://localhost:5174", "https://bitmatchapp.com", + "https://www.bitmatchapp.com", "https://api.bitmatchapp.com", ] diff --git a/backend/projects/models.py b/backend/projects/models.py index 4a3bd66..eedfb55 100644 --- a/backend/projects/models.py +++ b/backend/projects/models.py @@ -5,7 +5,7 @@ # PROJECTS MODEL class Project(models.Model): # Auto generated - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + id=models.CharField(primary_key=True,default=uuid.uuid4, editable=False, max_length=36) # REQUIRED title = models.CharField(max_length=255, blank=False, null=False) diff --git a/backend/projects/views.py b/backend/projects/views.py index 73bf3c6..b21bb57 100644 --- a/backend/projects/views.py +++ b/backend/projects/views.py @@ -27,7 +27,13 @@ def post(self, request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + print(serializer.errors) + error_details = { + "message": "Invalid project data provided.", + "errors": serializer.errors, + } + return Response(error_details, status=status.HTTP_400_BAD_REQUEST) # Get a single project by ID (GET) def get(self, request, pk): @@ -42,7 +48,13 @@ def put(self, request, pk): if serializer.is_valid(): serializer.save() return Response(serializer.data) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + print(serializer.errors) + error_details = { + "message": "Invalid project data provided.", + "errors": serializer.errors, + } + return Response(error_details, status=status.HTTP_400_BAD_REQUEST) # Delete a project by ID (DELETE) def delete(self, request, pk): diff --git a/frontend/bitmatch/eslint.config.js b/frontend/bitmatch/eslint.config.js index b0f220b..1b66b69 100644 --- a/frontend/bitmatch/eslint.config.js +++ b/frontend/bitmatch/eslint.config.js @@ -34,6 +34,7 @@ export default [ "warn", { allowConstantExport: true }, ], + "react/prop-types": "warn", }, }, ]; diff --git a/frontend/bitmatch/index.html b/frontend/bitmatch/index.html index 4958bd0..53292e0 100644 --- a/frontend/bitmatch/index.html +++ b/frontend/bitmatch/index.html @@ -2,7 +2,7 @@ - + BitMatch diff --git a/frontend/bitmatch/package-lock.json b/frontend/bitmatch/package-lock.json index 566e772..1b9f647 100644 --- a/frontend/bitmatch/package-lock.json +++ b/frontend/bitmatch/package-lock.json @@ -9,10 +9,16 @@ "version": "0.0.0", "dependencies": { "@clerk/clerk-react": "^5.24.0", + "@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-slot": "^1.1.2", + "@radix-ui/react-toggle": "^1.1.2", "axios": "^1.8.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.6.3", "lucide-react": "^0.475.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -928,6 +934,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1089,6 +1133,41 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "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/react-compose-refs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", @@ -1103,6 +1182,351 @@ } } }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "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-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", + "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-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-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "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-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "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-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "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-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "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-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "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-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-label": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz", + "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==", + "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/react-popover": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", + "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==", + "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-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-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "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-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@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-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "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-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "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-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "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-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@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-scroll-area": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz", + "integrity": "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@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-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "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-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", @@ -1120,6 +1544,139 @@ } } }, + "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", + "integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "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-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "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-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "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", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "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-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", @@ -1444,7 +2001,7 @@ "version": "19.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", - "dev": true, + "devOptional": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -1558,6 +2115,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -2194,6 +2763,12 @@ "node": ">=6" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2850,6 +3425,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz", + "integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.6.3", + "motion-utils": "^12.6.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2932,6 +3534,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3843,6 +4454,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz", + "integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.6.3" + } + }, + "node_modules/motion-utils": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz", + "integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4427,6 +5053,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz", @@ -4466,6 +5139,28 @@ "react-dom": ">=18" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-tabs": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.0.tgz", @@ -5377,6 +6072,49 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sync-external-store": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", diff --git a/frontend/bitmatch/package.json b/frontend/bitmatch/package.json index d3a095c..5d62722 100644 --- a/frontend/bitmatch/package.json +++ b/frontend/bitmatch/package.json @@ -11,10 +11,16 @@ }, "dependencies": { "@clerk/clerk-react": "^5.24.0", + "@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-slot": "^1.1.2", + "@radix-ui/react-toggle": "^1.1.2", "axios": "^1.8.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.6.3", "lucide-react": "^0.475.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/frontend/bitmatch/public/favicon.png b/frontend/bitmatch/public/favicon.png new file mode 100644 index 0000000..86a043f Binary files /dev/null and b/frontend/bitmatch/public/favicon.png differ diff --git a/frontend/bitmatch/src/App.jsx b/frontend/bitmatch/src/App.jsx index ce46024..1dbb200 100644 --- a/frontend/bitmatch/src/App.jsx +++ b/frontend/bitmatch/src/App.jsx @@ -1,107 +1,101 @@ -import { useEffect, useState } from "react"; -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { - SignedIn, - SignedOut, - SignInButton, - UserButton, - useUser, -} from "@clerk/clerk-react"; -import { Button } from "./components/ui/button"; + BrowserRouter as Router, + Routes, + Route, + useLocation, +} from "react-router-dom"; +import { SignedIn, SignedOut, useUser } from "@clerk/clerk-react"; + +import MainNavbar from "./components/navbar"; +import MainFooter from "./components/footer"; + +import LandingPage from "./views/LandingPage"; import HomePage from "./views/HomePage"; import ProjectListPage from "./views/ProjectListPage"; import ProjectDetailPage from "./views/IndividualProjectPage"; -import "./styles/global.css"; +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"; -export default function App() { - const { user } = useUser(); - const [showModal, setShowModal] = useState(false); - // eslint-disable-next-line no-unused-vars - const [showMaintenanceNote, setShowMaintenanceNote] = useState(true); // Toggle for maintenance note +import "./styles/global.css"; - useEffect(() => { - if (user && !sessionStorage.getItem("seenDevelopmentModal")) { - setShowModal(true); - sessionStorage.setItem("seenDevelopmentModal", "true"); // Store it for the session - } - }, [user]); +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"; return ( - -
- -
-

- Sign in to continue -

- {/* Maintenance Note */} - {showMaintenanceNote && ( -
- - Maintenance Notice: We are currently performing maintenance to - improve the performance and reliability of our service. Some - features may be temporarily unavailable. We appreciate your - patience and understanding as we work to enhance your - experience. - -
- )} - - - -
-
+
+ + {/* Landing or Home */} + + + + + + + + + } + /> - - {/* First Login Modal (Per Session) */} - {showModal && ( -
-
-

- Disclaimer -

-

- Bitmatch is in early development, so some features may not - work as expected. Thanks for your patience and support as we - continue to improve! -

-

- If you experience any issues, please email us at{" "} - - lqla@cpp.edu - - . -

- -
-
- )} + {/* Signed-in only routes */} + + +
+ } + /> + + + + } + /> + + + + } + /> -
- -
+ {/* Public pages */} + } /> + } /> + } /> - - } /> - } /> - } /> - - -
+ {/* Uncomment when ready */} + {/* + } /> + } /> + */} + +
+ ); +} + +export default function App() { + return ( + + + + ); } diff --git a/frontend/bitmatch/src/assets/group_students.png b/frontend/bitmatch/src/assets/group_students.png new file mode 100644 index 0000000..65d6cdd Binary files /dev/null and b/frontend/bitmatch/src/assets/group_students.png differ diff --git a/frontend/bitmatch/src/assets/logo.png b/frontend/bitmatch/src/assets/logo.png new file mode 100644 index 0000000..86a043f Binary files /dev/null and b/frontend/bitmatch/src/assets/logo.png differ diff --git a/frontend/bitmatch/src/components/footer.jsx b/frontend/bitmatch/src/components/footer.jsx new file mode 100644 index 0000000..542317a --- /dev/null +++ b/frontend/bitmatch/src/components/footer.jsx @@ -0,0 +1,93 @@ +import { Link } from "react-router-dom"; + +export default function Footer({ links = [], showSocial = true }) { + const defaultLinks = [ + { href: "/terms", label: "Terms" }, + { href: "/privacy", label: "Privacy" }, + { href: "/contact", label: "Contact" }, + ]; + + const footerLinks = links.length > 0 ? links : defaultLinks; + + return ( + + ); +} diff --git a/frontend/bitmatch/src/components/landing/Features.jsx b/frontend/bitmatch/src/components/landing/Features.jsx new file mode 100644 index 0000000..ccdd176 --- /dev/null +++ b/frontend/bitmatch/src/components/landing/Features.jsx @@ -0,0 +1,83 @@ +import React from "react"; +import { motion } from "framer-motion"; +import { fadeUp, containerStagger } from "./animations"; +import { + Briefcase, + Users, + Award, + BookOpen, + Search, + ArrowRight, +} from "lucide-react"; + +const features = [ + { + icon: , title: "Real-world Experience", + text: "Work on actual projects from companies and non-profits to gain practical experience." + }, + { + icon: , title: "Networking Opportunities", + text: "Connect with industry professionals, mentors, and like-minded students." + }, + { + icon: , title: "Portfolio Builder", + text: "Create an impressive portfolio of completed projects to showcase to future employers." + }, + { + icon: , title: "Academic Credit", + text: "Many projects qualify for course credit or satisfy academic requirements." + }, + { + icon: , title: "Personalized Matching", + text: "Our algorithm matches you with projects that fit your skills, interests, and goals." + }, + { + icon: , title: "Career Advancement", + text: "Gain the skills and experience employers are looking for in today's job market." + }, +]; + +export function Features() { + return ( +
+
+
+

+ Why Students Love Bitmatch +

+

+ We connect students with real-world projects that help them build skills, expand their portfolio, and prepare for their career. +

+
+ + + {features.map((feature, idx) => ( + + {React.cloneElement(feature.icon, { + className: "h-12 w-12 text-blue-600", + role: "img", + "aria-label": feature.title, + })} +

{feature.title}

+

{feature.text}

+
+ ))} +
+
+
+ ); +} diff --git a/frontend/bitmatch/src/components/landing/HeroSection.jsx b/frontend/bitmatch/src/components/landing/HeroSection.jsx new file mode 100644 index 0000000..b5a8b1c --- /dev/null +++ b/frontend/bitmatch/src/components/landing/HeroSection.jsx @@ -0,0 +1,54 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { motion } from "framer-motion"; +import groupImg from "../../assets/group_students.png"; +import { ArrowRight } from "lucide-react"; +import { fadeUp, containerStagger } from "./animations"; + +export function HeroSection({ user }) { + return ( + +
+
+ + Students collaborating on a project + + + +

+ Build Experience and Community Bit by Bit +

+

+ Find relevant roles for job experience or choose a project that’s meaningful, impactful and right for you. +

+
+ + + + + + +
+
+
+
+
+ ); +} diff --git a/frontend/bitmatch/src/components/landing/HowItWorks.jsx b/frontend/bitmatch/src/components/landing/HowItWorks.jsx new file mode 100644 index 0000000..fc0ebb9 --- /dev/null +++ b/frontend/bitmatch/src/components/landing/HowItWorks.jsx @@ -0,0 +1,59 @@ +import React from "react"; +import { motion } from "framer-motion"; +import { fadeUp, containerStagger } from "./animations"; + +const steps = [ + { + title: "Create Your Profile", + text: "Sign up and build your profile with your skills, interests, and academic background.", + }, + { + title: "Browse Projects", + text: "Explore available projects or get personalized recommendations based on your profile.", + }, + { + title: "Apply and Connect", + text: "Apply to projects that interest you and connect with project providers to get started.", + }, +]; + +export function HowItWorks() { + return ( +
+
+
+

How It Works

+

+ Finding and applying for projects is simple and straightforward. +

+
+ + + {steps.map((step, idx) => ( + +
+ {idx + 1} +
+

{step.title}

+

{step.text}

+
+ ))} +
+
+
+ ); +} diff --git a/frontend/bitmatch/src/components/landing/animations.js b/frontend/bitmatch/src/components/landing/animations.js new file mode 100644 index 0000000..47bfbef --- /dev/null +++ b/frontend/bitmatch/src/components/landing/animations.js @@ -0,0 +1,20 @@ +// animations.js +export const fadeUp = { + hidden: { opacity: 0, y: 30 }, + visible: { opacity: 1, y: 0 }, + }; + + export const fadeInModal = { + hidden: { opacity: 0, scale: 0.95 }, + visible: { opacity: 1, scale: 1 }, + }; + + export const containerStagger = { + hidden: {}, + visible: { + transition: { + staggerChildren: 0.15, + }, + }, + }; + \ No newline at end of file diff --git a/frontend/bitmatch/src/components/navbar.jsx b/frontend/bitmatch/src/components/navbar.jsx new file mode 100644 index 0000000..677e10f --- /dev/null +++ b/frontend/bitmatch/src/components/navbar.jsx @@ -0,0 +1,127 @@ +import { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { GraduationCap, Menu, X } from "lucide-react"; +import { + SignedIn, + SignedOut, + UserButton, + SignInButton, + useUser, +} from "@clerk/clerk-react"; +import { Button } from "@/components/ui/button"; +import logo from "@/assets/logo.png"; + +export default function Navbar({ links = [] }) { + const defaultLinks = [ + { href: "/about", label: "About" }, + { href: "/browse", label: "Browse" }, + { href: "/contact", label: "Contact" }, + ]; + + const { user } = useUser(); + const [showModal, setShowModal] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + + useEffect(() => { + if (user && !sessionStorage.getItem("seenDevelopmentModal")) { + setShowModal(true); + sessionStorage.setItem("seenDevelopmentModal", "true"); + } + }, [user]); + + const navLinks = links.length > 0 ? links : defaultLinks; + + return ( + <> + {/* Navbar */} +
+
+ {/* Logo */} + + Bitmatch logo +

+ BITMATCH +

+ + + {/* Desktop Nav */} + + + {/* Auth & Hamburger */} +
+ + + + + + + + + + + {/* Hamburger Icon */} + +
+
+
+ + {/* Mobile Drop Down Menu - Appears Below Header */} + {menuOpen && ( +
+ {navLinks.map((link) => ( + setMenuOpen(false)} + className="block text-gray-700 hover:text-blue-600 font-medium text-lg" + > + {link.label} + + ))} +
+ )} + + {/* Development Modal */} + {showModal && ( +
+
+

Disclaimer

+

+ Bitmatch is in early development, so some features may not work as + expected. Thanks for your patience and support as we continue to + improve! +

+ +
+
+ )} + + ); +} diff --git a/frontend/bitmatch/src/components/project/DiscussionCard.jsx b/frontend/bitmatch/src/components/project/DiscussionCard.jsx new file mode 100644 index 0000000..a3e2bd4 --- /dev/null +++ b/frontend/bitmatch/src/components/project/DiscussionCard.jsx @@ -0,0 +1,125 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; + +export function ReplyForm({ postId, onCancel, onSubmit }) { + const [content, setContent] = useState(""); + + const handleSubmit = () => { + if (content.trim()) { + onSubmit(postId, content); + setContent(""); + } + }; + + return ( +
+
+ +
+ Max Characters: 255 + Character Count: {shortDescription.length} +
+ +

+ + + +
+ Max Characters: 1000 + Character Count: {fullDescription.length} +
+
+ +
+ +
+ { + setNewRole(e.target.value); + setRoleError(""); + }} + className="flex-grow border rounded-md p-2 focus:ring-blue-500 focus:border-blue-500" + placeholder="e.g., Frontend Developer, UI/UX Designer" + /> + +
+ + {roleError && ( +

{roleError}

+ )} + +
+ {roles.length === 0 ? ( +

+ No roles added yet. +

+ ) : ( + roles.map((role, index) => ( +
+ {role.title} + +
+ )) + )} +
+
+ + {error && ( +

+ {error} +

+ )} + {successMessage && ( +

+ {successMessage} +

+ )} +
+ +
+
+ + + ); +} diff --git a/frontend/bitmatch/src/views/IndividualProjectPage.jsx b/frontend/bitmatch/src/views/IndividualProjectPage.jsx index 6521536..a06b050 100644 --- a/frontend/bitmatch/src/views/IndividualProjectPage.jsx +++ b/frontend/bitmatch/src/views/IndividualProjectPage.jsx @@ -1,10 +1,21 @@ -import { ChevronRight, ChevronLeft, Plus, Edit, Icon } from "lucide-react"; +import { + ChevronRight, + ChevronLeft, + Plus, + Edit, + Icon, + ThumbsUp, + UserRound, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { MemberCard } from "@/components/project/MemberCard"; +import { PositionCard } from "@/components/project/PositionCard"; import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; -import Modal from "@/components/project/Modal"; +import { DiscussionPost, ReplyForm } from "@/components/project/DiscussionCard"; +import { EditProjectDialog } from "@/components/project/EditProjectDialog"; const SERVER_HOST = import.meta.env.VITE_SERVER_HOST; import axios from "axios"; @@ -25,7 +36,6 @@ const fetchProjectInfo = async (id) => { } const projectData = await response.json(); - console.log(projectData); return projectData; } catch (error) { @@ -34,14 +44,19 @@ const fetchProjectInfo = async (id) => { } }; +const editProjectInfo = async (id) => {}; + const ProjectDetailPage = () => { const { id } = useParams(); // Access the dynamic `id` parameter from the URL const [project, setProject] = useState(null); // State to store project details const [loading, setLoading] = useState(true); // State to handle loading state const [error, setError] = useState(null); // State to handle errors const [currentImageIndex, setCurrentImageIndex] = useState(0); - const [isModalOpen, setIsModalOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState("overview"); + const [discussions, setDiscussions] = useState([]); + const [showCommentForm, setShowCommentForm] = useState(false); + const [replyingTo, setReplyingTo] = useState(null); useEffect(() => { // Fetch project details when the component mounts or the `id` changes @@ -64,6 +79,42 @@ const ProjectDetailPage = () => { loadProjectInfo(); }, [id]); + const handleSave = async (data) => { + console.log("Saving project data:", data); + + const formData = new FormData(); + + formData.append("title", data.title); + formData.append("group", data.group); + formData.append("institution", data.institution); + formData.append("description", data.description); + formData.append("full_description", data.full_description); + formData.append("positions", JSON.stringify(data.positions)); + + if (data.new_image) { + formData.append("image_url", data.new_image); + } + + try { + const response = await axios.put( + `${SERVER_HOST}/projects/${data.id}/`, + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + + alert("Project updated successfully!"); + editProjectInfo(response.data); + window.location.reload(); + } catch (error) { + console.error("Error updating project:", error); + alert("Failed to update the project. Please try again."); + } + }; + const nextImage = () => { if (project.images) { setCurrentImageIndex((prev) => @@ -84,6 +135,100 @@ const ProjectDetailPage = () => { setCurrentImageIndex(index); }; + const handleAddComment = () => { + setShowCommentForm(true); + setReplyingTo(null); + }; + + const handleCancelComment = () => { + setShowCommentForm(false); + setReplyingTo(null); + }; + + const handleSubmitComment = (postId, content) => { + if (postId) { + // Add reply to existing post + const updatedDiscussions = discussions.map((discussion) => { + if (discussion.id === postId) { + return { + ...discussion, + replies: [ + ...(discussion.replies || []), + { + id: Date.now().toString(), + parentId: postId, + author: { + name: "Current User", + title: "Project Member", + profileImage: "", + }, + content, + datePosted: new Date().toLocaleString(), + }, + ], + }; + } + return discussion; + }); + setDiscussions(updatedDiscussions); + } else { + // Add new post + const newPost = { + id: Date.now().toString(), + author: { + name: "Current User", + title: "Project Member", + profileImage: "", + }, + content, + datePosted: new Date().toLocaleString(), + replies: [], + }; + setDiscussions([...discussions, newPost]); + } + setShowCommentForm(false); + setReplyingTo(null); + }; + + const handleReplyToPost = (id) => { + setReplyingTo(id); + }; + + const handleDeletePost = (id) => { + // In a real app, this would show a confirmation dialog + if (confirm(`Delete post with ID ${id}?`)) { + // Check if it's a main post or a reply + const isMainPost = discussions.some((discussion) => discussion.id === id); + + if (isMainPost) { + // Delete the main post and all its replies + const updatedDiscussions = discussions.filter( + (discussion) => discussion.id !== id + ); + setDiscussions(updatedDiscussions); + } else { + // Delete a reply + const updatedDiscussions = discussions.map((discussion) => { + if ( + discussion.replies && + discussion.replies.some((reply) => reply.id === id) + ) { + return { + ...discussion, + replies: discussion.replies.filter((reply) => reply.id !== id), + }; + } + return discussion; + }); + setDiscussions(updatedDiscussions); + } + } + }; + + const handleReaction = (id) => { + alert(`Add reaction to post with ID ${id}`); + }; + // Loading state if (loading) { return ( @@ -119,19 +264,19 @@ const ProjectDetailPage = () => { onClick={() => window.history.back()} > - Back + Back to Projects {/* Main content */}
-

{project.title}

+

{project.title}

{/* Project showcase */} -
+
{/* Image Slider */} -
+
{project.images && project.images.length > 0 ? ( <>
@@ -142,7 +287,7 @@ const ProjectDetailPage = () => { alt="Project image" className="object-cover" /> -
+
-
-

- Slider -
- Image snapshots are below -

-
+
) : ( No images available @@ -175,7 +314,7 @@ const ProjectDetailPage = () => {
{/* Project info */}
-
+
{project.image_url ? ( { Cover Image goes here )}
-
+
From

{project.group}

{project.description}

-
+ {project.likes_count} Likes
-
+ {project.followers_count} Followers
@@ -206,7 +349,7 @@ const ProjectDetailPage = () => {
{/* Thumbnails */} -
+
{project.images && project.images.map((image, index) => ( @@ -220,8 +363,8 @@ const ProjectDetailPage = () => { {`Pic
@@ -231,149 +374,451 @@ const ProjectDetailPage = () => { {/* Tabs */} { + const tabNames = [ + "overview", + "updates", + "members", + "wanted", + "discussions", + "contact", + "edit", + ]; + const selectedTab = tabNames[index]; + + if (selectedTab === "edit") { + setIsOpen(true); // Open the dialog + setActiveTab("overview"); // Stay on the default tab (e.g., Overview) + } else { + setActiveTab(selectedTab); // Change tab normally + } + }} > - + Overview Updates Members Wanted Discussions Contact + + { + setIsOpen(true); + e.preventDefault(); + }} + > + Edit Project + + +
-

Overview

+

Overview

-

+

Background & More Details About the Project -

+
-
- This space will be filled in by the owner -
-

{project.description}

- - setIsModalOpen(false)} - > -

Edit Project

-
-

Title

- -

Description

- -
- -
+

{project.full_description}

-
-

- Updates content will go here -

+
+
+

+ Updates{" "} + (W.I.P) +

+
+

Title

+ + {showCommentForm ? ( + + ) : ( +
+
+
+

+ Add your comments here +

+
+
+
+ + +
+
+ )} +
+
-
-

- Members content will go here -

+
+ {/* Search and Add Member Section */} +
+
+ + +
+
+ + {/* Members List Section */} +
+

+ Students Working on This Project{" "} + (W.I.P) +

+ +
+ {/* Member 1 */} + + {/* Member 2 */} + + {/* Member 3 */} + +
+
-
-

- Wanted content will go here -

+
+ {/* Wanted Header */} +

+ Wanted{" "} + (W.I.P) +

+ {/* Positions Section */} +
+
+

+ Positions Needed for This Project +

+ +
+ +
+ + +
+
- -
-

- Discussions content will go here -

+ +
+ {/* Discussions Header */} +
+
+ + Discussions{" "} + (W.I.P) + +
+
+ + {/* Comment Form */} +
+ {showCommentForm ? ( + + ) : ( +
+
+
+

+ Add your comments here +

+
+
+
+ + +
+
+ )} + + {/* Discussion Posts */} +
+ {discussions.length > 0 ? ( + discussions.map((discussion) => ( +
+ + + {replyingTo === discussion.id && ( +
+ +
+ )} + + {discussion.replies && + discussion.replies.map((reply) => ( +
+ + + {replyingTo === reply.id && ( +
+ +
+ )} +
+ ))} +
+ )) + ) : ( +

+ No discussions yet. Start the conversation! +

+ )} +
+
-
-

- Contact content will go here -

+
+
+

+ Contact The Owner of This Project{" "} + (W.I.P) +

+

Full Name

+ +

Email Address

+ +

Subject

+ +

Description

+ + +
+
{/* Footer */}
-

Footer

+

diff --git a/frontend/bitmatch/src/views/LandingPage.jsx b/frontend/bitmatch/src/views/LandingPage.jsx new file mode 100644 index 0000000..7d97d1a --- /dev/null +++ b/frontend/bitmatch/src/views/LandingPage.jsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from "react"; +import { useUser } from "@clerk/clerk-react"; +import { motion } from "framer-motion"; + +import { HeroSection } from "../components/Landing/HeroSection"; +import { HowItWorks } from "../components/Landing/HowItWorks"; +import { Features } from "../components/Landing/Features"; +import { Button } from "@/components/ui/button"; + +export default function LandingPage() { + const { user } = useUser(); + const [showModal, setShowModal] = useState(true); + + // Scroll to top on mount + useEffect(() => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }, []); + + // Show development modal once per session for signed-in users + useEffect(() => { + if (user && !sessionStorage.getItem("seenDevelopmentModal")) { + setShowModal(true); + sessionStorage.setItem("seenDevelopmentModal", "true"); + } + }, [user]); + + return ( +
+ {/* Landing Page Sections */} + + + + + {/* Development Notice Modal */} + {showModal && setShowModal(false)} />} +
+ ); +} + +// Extracted Modal for clarity +function Modal({ onClose }) { + return ( +
+ + +

+ BitMatch is currently undergoing maintenance, so some features may not + work as expected. +

+ +
+
+ ); +} diff --git a/frontend/bitmatch/src/views/OnboardPage.jsx b/frontend/bitmatch/src/views/OnboardPage.jsx new file mode 100644 index 0000000..c738e89 --- /dev/null +++ b/frontend/bitmatch/src/views/OnboardPage.jsx @@ -0,0 +1,81 @@ +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"; + +export default function OnboardPage() { + const navigate = useNavigate(); + const [formData, setFormData] = useState({ + role: "", + interests: "", + bio: "", + }); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + // You can send this data to Supabase, Clerk user metadata, or your backend here + console.log("Onboarding data submitted:", formData); + + // Redirect to dashboard or home + navigate("/dashboard"); + }; + + return ( +
+
+

Work in Progress!

+

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

+ +
+
+ + +
+ +
+ + +
+ +
+ +