diff --git a/backend/core/settings.py b/backend/core/settings.py index ba1eb69..84cd4c5 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -10,6 +10,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.2/ref/settings/ """ +from datetime import timedelta import os from pathlib import Path from decouple import config, Csv @@ -80,6 +81,11 @@ REST_FRAMEWORK = { REST_USE_JWT = True +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=3), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), +} + GOOGLE_CLIENT_ID = config('GOOGLE_CLIENT_ID', default='fake-client-id') GOOGLE_CLIENT_SECRET = config('GOOGLE_CLIENT_SECRET', default='fake-client-secret') @@ -226,4 +232,27 @@ LOGOUT_REDIRECT_URL = '/' AUTH_USER_MODEL = "users.CustomUser" -ACCOUNT_EMAIL_REQUIRED = True \ No newline at end of file +ACCOUNT_EMAIL_REQUIRED = True + +# Storages + +AWS_ACCESS_KEY_ID = config('AMAZON_S3_ACCESS_KEY', default='fake-access-key') +AWS_SECRET_ACCESS_KEY = config('AMAZON_S3_SECRET_ACCESS_KEY', default='fake-secret-access-key') +AWS_STORAGE_BUCKET_NAME = config('BUCKET_NAME', default='fake-bucket-name') +AWS_DEFAULT_ACL = 'public-read' +AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com' +AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'} + +MEDIA_URL = '/mediafiles/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles') + +STORAGES = { + "default": { + "BACKEND": "storages.backends.s3.S3Storage", + "OPTIONS": { + }, + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} \ No newline at end of file diff --git a/backend/users/migrations/0003_customuser_profile_pic.py b/backend/users/migrations/0003_customuser_profile_pic.py new file mode 100644 index 0000000..1604578 --- /dev/null +++ b/backend/users/migrations/0003_customuser_profile_pic.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-11-04 19:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_customuser_refresh_token'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='profile_pic', + field=models.ImageField(blank=True, default='profile_pics/default.png', null=True, upload_to='profile_pics'), + ), + ] diff --git a/backend/users/models.py b/backend/users/models.py index 64d976d..5b5b176 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -13,6 +13,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): first_name = models.CharField(max_length=150, blank=True) start_date = models.DateTimeField(default=timezone.now) about = models.TextField(_('about'), max_length=500, blank=True) + profile_pic = models.ImageField(upload_to='profile_pics', null=True, blank=True, default='profile_pics/default.png') is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) diff --git a/backend/users/serializers.py b/backend/users/serializers.py index a25e8ec..962d789 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -7,12 +7,12 @@ class CustomUserSerializer(serializers.ModelSerializer): Serializer for CustomUser model. """ email = serializers.EmailField(required=True) - username = serializers.CharField(required=True) + username = serializers.CharField() password = serializers.CharField(min_length=8, write_only=True) class Meta: model = CustomUser - fields = ('email', 'password') + fields = ('email', 'username', 'password') extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): @@ -25,3 +25,25 @@ class CustomUserSerializer(serializers.ModelSerializer): instance.set_password(password) instance.save() return instance + + +class UpdateProfileSerializer(serializers.ModelSerializer): + """ + Serializer for updating user profile. + """ + profile_pic = serializers.ImageField(required=False) + first_name = serializers.CharField(max_length=255, required=False) + about = serializers.CharField(required=False) + + class Meta: + model = CustomUser + fields = ('profile_pic', 'first_name', 'about') + + def update(self, instance, validated_data): + """ + Update an existing user's profile. + """ + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + return instance \ No newline at end of file diff --git a/backend/users/urls.py b/backend/users/urls.py index 2b44459..474da31 100644 --- a/backend/users/urls.py +++ b/backend/users/urls.py @@ -1,6 +1,7 @@ from django.urls import path -from users.views import CustomUserCreate +from users.views import CustomUserCreate, CustomUserProfileUpdate urlpatterns = [ path('user/create/', CustomUserCreate.as_view(), name="create_user"), + path('user/update/', CustomUserProfileUpdate.as_view(), name='update_user') ] \ No newline at end of file diff --git a/backend/users/views.py b/backend/users/views.py index 6f020b6..af201b7 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -1,12 +1,14 @@ """This module defines API views for user creation""" from rest_framework import status -from rest_framework.permissions import AllowAny +from rest_framework.permissions import AllowAny, IsAuthenticated + from rest_framework.response import Response from rest_framework.views import APIView +from rest_framework.parsers import MultiPartParser -from .serializers import CustomUserSerializer - +from users.serializers import CustomUserSerializer, UpdateProfileSerializer +from users.models import CustomUser class CustomUserCreate(APIView): """ @@ -25,3 +27,34 @@ class CustomUserCreate(APIView): if user: return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class CustomUserProfileUpdate(APIView): + """ + Custom User Profile Update View. + """ + parser_classes = (MultiPartParser,) + permission_classes = (IsAuthenticated,) + + def get(self, request): + user = request.user + image_url = user.profile_pic.url + username = user.username + + data = { + 'image_url': image_url, + 'username': username + } + + return Response(data) + + def post(self, request): + if not CustomUser.objects.filter(email=request.user.email).exists(): + return Response ({ + 'error': 'User does not exist' + }, status=status.HTTP_404_NOT_FOUND) + serializer = UpdateProfileSerializer(request.user, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 7ff9906..793cfe0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,8 +12,6 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@material-ui/core": "^4.12.4", - "@material-ui/icons": "^4.11.3", "@mui/icons-material": "^5.14.15", "@mui/material": "^5.14.15", "@mui/system": "^5.14.15", @@ -27,15 +25,16 @@ "react": "^18.2.0", "react-bootstrap": "^2.9.1", "react-dom": "^18.2.0", - "react-google-login": "^5.2.2", "react-icons": "^4.11.0", "react-router-dom": "^6.17.0" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.10", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.3", "autoprefixer": "^10.4.16", + "daisyui": "^3.9.4", "eslint": "^8.45.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 18613e9..67325cd 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,12 +11,6 @@ dependencies: '@emotion/styled': specifier: ^11.11.0 version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) - '@material-ui/core': - specifier: ^4.12.4 - version: 4.12.4(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) - '@material-ui/icons': - specifier: ^4.11.3 - version: 4.11.3(@material-ui/core@4.12.4)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) '@mui/icons-material': specifier: ^5.14.15 version: 5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0) @@ -56,9 +50,6 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - react-google-login: - specifier: ^5.2.2 - version: 5.2.2(react-dom@18.2.0)(react@18.2.0) react-icons: specifier: ^4.11.0 version: 4.11.0(react@18.2.0) @@ -67,6 +58,9 @@ dependencies: version: 6.17.0(react-dom@18.2.0)(react@18.2.0) devDependencies: + '@tailwindcss/typography': + specifier: ^0.5.10 + version: 0.5.10(tailwindcss@3.3.5) '@types/react': specifier: ^18.2.15 version: 18.2.33 @@ -79,6 +73,9 @@ devDependencies: autoprefixer: specifier: ^10.4.16 version: 10.4.16(postcss@8.4.31) + daisyui: + specifier: ^3.9.4 + version: 3.9.4 eslint: specifier: ^8.45.0 version: 8.52.0 @@ -364,10 +361,6 @@ packages: stylis: 4.2.0 dev: false - /@emotion/hash@0.8.0: - resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - dev: false - /@emotion/hash@0.9.1: resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} dev: false @@ -785,132 +778,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@material-ui/core@4.12.4(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==} - engines: {node: '>=8.0.0'} - deprecated: Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5. - peerDependencies: - '@types/react': ^16.8.6 || ^17.0.0 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@material-ui/styles': 4.11.5(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) - '@material-ui/system': 4.12.2(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) - '@material-ui/types': 5.1.0(@types/react@18.2.33) - '@material-ui/utils': 4.11.3(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.33 - '@types/react-transition-group': 4.4.8 - clsx: 1.2.1 - hoist-non-react-statics: 3.3.2 - popper.js: 1.16.1-lts - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 16.13.1 - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - dev: false - - /@material-ui/icons@4.11.3(@material-ui/core@4.12.4)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==} - engines: {node: '>=8.0.0'} - peerDependencies: - '@material-ui/core': ^4.0.0 - '@types/react': ^16.8.6 || ^17.0.0 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@material-ui/core': 4.12.4(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.33 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@material-ui/styles@4.11.5(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==} - engines: {node: '>=8.0.0'} - deprecated: Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5. - peerDependencies: - '@types/react': ^16.8.6 || ^17.0.0 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@emotion/hash': 0.8.0 - '@material-ui/types': 5.1.0(@types/react@18.2.33) - '@material-ui/utils': 4.11.3(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.33 - clsx: 1.2.1 - csstype: 2.6.21 - hoist-non-react-statics: 3.3.2 - jss: 10.10.0 - jss-plugin-camel-case: 10.10.0 - jss-plugin-default-unit: 10.10.0 - jss-plugin-global: 10.10.0 - jss-plugin-nested: 10.10.0 - jss-plugin-props-sort: 10.10.0 - jss-plugin-rule-value-function: 10.10.0 - jss-plugin-vendor-prefixer: 10.10.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@material-ui/system@4.12.2(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==} - engines: {node: '>=8.0.0'} - peerDependencies: - '@types/react': ^16.8.6 || ^17.0.0 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@material-ui/utils': 4.11.3(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.33 - csstype: 2.6.21 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@material-ui/types@5.1.0(@types/react@18.2.33): - resolution: {integrity: sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==} - peerDependencies: - '@types/react': '*' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.33 - dev: false - - /@material-ui/utils@4.11.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==} - engines: {node: '>=8.0.0'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - dependencies: - '@babel/runtime': 7.23.2 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 16.13.1 - dev: false - /@mui/base@5.0.0-beta.21(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-eTKWx3WV/nwmRUK4z4K1MzlMyWCsi3WJ3RtV4DiXZeRh4qd4JCyp1Zzzi8Wv9xM4dEBmqQntFoei716PzwmFfA==} engines: {node: '>=12.0.0'} @@ -1173,6 +1040,18 @@ packages: tslib: 2.6.2 dev: false + /@tailwindcss/typography@0.5.10(tailwindcss@3.3.5): + resolution: {integrity: sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.3.5 + dev: true + /@types/babel__core@7.20.3: resolution: {integrity: sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==} dependencies: @@ -1525,11 +1404,6 @@ packages: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} dev: false - /clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false - /clsx@2.0.0: resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} @@ -1554,6 +1428,10 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1598,12 +1476,12 @@ packages: which: 2.0.2 dev: true - /css-vendor@2.0.8: - resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==} + /css-selector-tokenizer@0.8.0: + resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==} dependencies: - '@babel/runtime': 7.23.2 - is-in-browser: 1.1.3 - dev: false + cssesc: 3.0.0 + fastparse: 1.1.2 + dev: true /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -1611,13 +1489,22 @@ packages: hasBin: true dev: true - /csstype@2.6.21: - resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} - dev: false - /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /daisyui@3.9.4: + resolution: {integrity: sha512-fvi2RGH4YV617/6DntOVGcOugOPym9jTGWW2XySb5ZpvdWO4L7bEG77VHirrnbRUEWvIEVXkBpxUz2KFj0rVnA==} + engines: {node: '>=16.9.0'} + dependencies: + colord: 2.9.3 + css-selector-tokenizer: 0.8.0 + postcss: 8.4.31 + postcss-js: 4.0.1(postcss@8.4.31) + tailwindcss: 3.3.5 + transitivePeerDependencies: + - ts-node + dev: true + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1995,6 +1882,10 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fastparse@1.1.2: + resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} + dev: true + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -2255,10 +2146,6 @@ packages: react-is: 16.13.1 dev: false - /hyphenate-style-name@1.0.4: - resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} - dev: false - /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -2384,10 +2271,6 @@ packages: is-extglob: 2.1.1 dev: true - /is-in-browser@1.1.3: - resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==} - dev: false - /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} dev: true @@ -2531,68 +2414,6 @@ packages: hasBin: true dev: true - /jss-plugin-camel-case@10.10.0: - resolution: {integrity: sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==} - dependencies: - '@babel/runtime': 7.23.2 - hyphenate-style-name: 1.0.4 - jss: 10.10.0 - dev: false - - /jss-plugin-default-unit@10.10.0: - resolution: {integrity: sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-global@10.10.0: - resolution: {integrity: sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-nested@10.10.0: - resolution: {integrity: sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - tiny-warning: 1.0.3 - dev: false - - /jss-plugin-props-sort@10.10.0: - resolution: {integrity: sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-rule-value-function@10.10.0: - resolution: {integrity: sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - tiny-warning: 1.0.3 - dev: false - - /jss-plugin-vendor-prefixer@10.10.0: - resolution: {integrity: sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==} - dependencies: - '@babel/runtime': 7.23.2 - css-vendor: 2.0.8 - jss: 10.10.0 - dev: false - - /jss@10.10.0: - resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==} - dependencies: - '@babel/runtime': 7.23.2 - csstype: 3.1.2 - is-in-browser: 1.1.3 - tiny-warning: 1.0.3 - dev: false - /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2637,6 +2458,14 @@ packages: p-locate: 5.0.0 dev: true + /lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + dev: true + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -2872,10 +2701,6 @@ packages: engines: {node: '>= 6'} dev: true - /popper.js@1.16.1-lts: - resolution: {integrity: sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==} - dev: false - /postcss-import@15.1.0(postcss@8.4.31): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -2925,6 +2750,14 @@ packages: postcss-selector-parser: 6.0.13 dev: true + /postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + /postcss-selector-parser@6.0.13: resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} engines: {node: '>=4'} @@ -3018,19 +2851,6 @@ packages: scheduler: 0.23.0 dev: false - /react-google-login@5.2.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - peerDependencies: - react: ^16 || ^17 - react-dom: ^16 || ^17 - dependencies: - '@types/react': 18.2.33 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /react-icons@4.11.0(react@18.2.0): resolution: {integrity: sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==} peerDependencies: @@ -3396,10 +3216,6 @@ packages: any-promise: 1.3.0 dev: true - /tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - dev: false - /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7333c4a..e9bb13b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,12 +2,11 @@ import './App.css'; import { BrowserRouter, Route, Routes, Link } from 'react-router-dom'; import TestAuth from './components/testAuth'; -import IconSideNav from './components/IconSideNav'; -import AuthenticantionPage from './components/authentication/AuthenticationPage'; +import LoginPage from './components/authentication/LoginPage'; import SignUpPage from './components/authentication/SignUpPage'; import NavBar from './components/Nav/Navbar'; import Home from './components/Home'; - +import ProfileUpdate from './components/ProfileUpdatePage' const App = () => { return ( @@ -16,14 +15,12 @@ const App = () => { }/> - }/> + }/> }/> }/> + }/> - {/*
- -
*/} ); } diff --git a/frontend/src/api/UserProfileApi.jsx b/frontend/src/api/UserProfileApi.jsx new file mode 100644 index 0000000..ca80fd1 --- /dev/null +++ b/frontend/src/api/UserProfileApi.jsx @@ -0,0 +1,21 @@ +import axios from 'axios'; + +const ApiUpdateUserProfile = async (formData) => { + try { + const response = await axios.post('http://127.0.1:8000/api/user/update/', formData, { + headers: { + 'Authorization': "Bearer " + localStorage.getItem('access_token'), + 'Content-Type': 'multipart/form-data', + }, + }); + + console.log(response.data); + + return response.data; + } catch (error) { + console.error('Error updating user profile:', error); + throw error; + } +}; + +export { ApiUpdateUserProfile }; diff --git a/frontend/src/components/ProfileUpdatePage.jsx b/frontend/src/components/ProfileUpdatePage.jsx new file mode 100644 index 0000000..06a0213 --- /dev/null +++ b/frontend/src/components/ProfileUpdatePage.jsx @@ -0,0 +1,107 @@ +import React, { useState, useRef } from 'react'; +import { ApiUpdateUserProfile } from '../api/UserProfileApi'; + +function ProfileUpdate() { + const [file, setFile] = useState(null); + const [username, setUsername] = useState(''); + const [fullName, setFullName] = useState(''); + const [about, setAbout] = useState(''); + const defaultImage = 'https://i1.sndcdn.com/artworks-cTz48e4f1lxn5Ozp-L3hopw-t500x500.jpg'; + const fileInputRef = useRef(null); + + const handleImageUpload = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const handleFileChange = (e) => { + const selectedFile = e.target.files[0]; + if (selectedFile) { + setFile(selectedFile); + } + }; + + const handleSave = () => { + const formData = new FormData(); + formData.append('profile_pic', file); + formData.append('first_name', username); + formData.append('about', about); + + ApiUpdateUserProfile(formData); + }; + + return ( +
+ {/* Profile Image */} +
+ +
+ {file ? ( + Profile + ) : ( + <> + Default + + + + )} +
+
+ + {/* Username Field */} +
+ + setUsername(e.target.value)} + /> +
+ + {/* Full Name Field */} +
+ + setFullName(e.target.value)} + /> +
+ + {/* About Field */} +
+ +