diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index 3faea0f04..b7507cd94 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -44,6 +44,10 @@ jobs: working-directory: frontend run: npm install + - name: Print npm log + if: failure() + run: cat /home/runner/.npm/_logs/*.log + - name: Build Frontend working-directory: frontend run: npm run build diff --git a/.gitignore b/.gitignore index 8e4150bdb..2c65e9a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ # dependencies -*/node_modules +node_modules */.pnp *.pnp.js @@ -44,4 +44,13 @@ *yarn-debug.log* *yarn-error.log* -node_modules \ No newline at end of file +# Jamshidbek +to-do.txt +jsonuser.json + +# backend/authentication +backend/authentication/images/* + +# media +media +node_modules diff --git a/backend.sh b/backend.sh new file mode 100755 index 000000000..c33b44620 --- /dev/null +++ b/backend.sh @@ -0,0 +1,2 @@ +cd backend && source venv/bin/activate +python manage.py makemigrations && python manage.py migrate \ No newline at end of file diff --git a/backend/authentication/admin.py b/backend/authentication/admin.py index 8c38f3f3d..00d4f380f 100644 --- a/backend/authentication/admin.py +++ b/backend/authentication/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from .models import User # Register your models here. + +admin.site.register(User) \ No newline at end of file diff --git a/backend/authentication/migrations/0001_initial.py b/backend/authentication/migrations/0001_initial.py index 97c536389..946880240 100644 --- a/backend/authentication/migrations/0001_initial.py +++ b/backend/authentication/migrations/0001_initial.py @@ -1,5 +1,8 @@ -# Generated by Django 5.0 on 2023-12-10 07:48 +# Generated by Django 5.0 on 2023-12-19 09:26 +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone from django.db import migrations, models @@ -8,6 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0001_initial'), ] operations = [ @@ -15,10 +19,26 @@ class Migration(migrations.Migration): name='User', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('username', models.CharField(max_length=255)), - ('display_name', models.CharField(max_length=255)), - ('email', models.EmailField(max_length=254)), - ('picture', models.URLField()), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), ], ), ] diff --git a/backend/authentication/migrations/0002_alter_user_id.py b/backend/authentication/migrations/0002_alter_user_id.py deleted file mode 100644 index 9169e7b8c..000000000 --- a/backend/authentication/migrations/0002_alter_user_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0 on 2023-12-13 10:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='id', - field=models.DecimalField(decimal_places=0, max_digits=10, primary_key=True, serialize=False, unique=True), - ), - ] diff --git a/backend/authentication/migrations/0002_user_losses_user_total_matches_user_wins.py b/backend/authentication/migrations/0002_user_losses_user_total_matches_user_wins.py new file mode 100644 index 000000000..87d84355a --- /dev/null +++ b/backend/authentication/migrations/0002_user_losses_user_total_matches_user_wins.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0 on 2023-12-20 09:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='losses', + field=models.PositiveIntegerField(default=0), + ), + migrations.AddField( + model_name='user', + name='total_matches', + field=models.PositiveIntegerField(default=0), + ), + migrations.AddField( + model_name='user', + name='wins', + field=models.PositiveIntegerField(default=0), + ), + ] diff --git a/backend/authentication/migrations/0003_delete_user.py b/backend/authentication/migrations/0003_delete_user.py deleted file mode 100644 index 1ed913791..000000000 --- a/backend/authentication/migrations/0003_delete_user.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.0 on 2023-12-15 09:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0002_alter_user_id'), - # ('leaderboard', '0003_delete_friendship'), - ] - - operations = [ - migrations.DeleteModel( - name='User', - ), - ] diff --git a/backend/authentication/migrations/0003_user_picture.py b/backend/authentication/migrations/0003_user_picture.py new file mode 100644 index 000000000..89dadbd89 --- /dev/null +++ b/backend/authentication/migrations/0003_user_picture.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2023-12-20 10:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0002_user_losses_user_total_matches_user_wins'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='picture', + field=models.ImageField(blank=True, default='default_profile_picture.PNG', null=True, upload_to='authentication/images/'), + ), + ] diff --git a/backend/authentication/migrations/0004_rename_picture_user_profile_picture.py b/backend/authentication/migrations/0004_rename_picture_user_profile_picture.py new file mode 100644 index 000000000..25d166d19 --- /dev/null +++ b/backend/authentication/migrations/0004_rename_picture_user_profile_picture.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2023-12-20 10:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0003_user_picture'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='picture', + new_name='profile_picture', + ), + ] diff --git a/backend/authentication/migrations/0005_user_title.py b/backend/authentication/migrations/0005_user_title.py new file mode 100644 index 000000000..9d72bc258 --- /dev/null +++ b/backend/authentication/migrations/0005_user_title.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2023-12-21 10:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0004_rename_picture_user_profile_picture'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='title', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/backend/authentication/migrations/0006_alter_user_profile_picture.py b/backend/authentication/migrations/0006_alter_user_profile_picture.py new file mode 100644 index 000000000..5c3def6c2 --- /dev/null +++ b/backend/authentication/migrations/0006_alter_user_profile_picture.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2023-12-21 12:39 + +import authentication.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0005_user_title'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='profile_picture', + field=models.ImageField(blank=True, default='default_profile_picture.PNG', null=True, upload_to=authentication.models.user_profile_picture_path), + ), + ] diff --git a/backend/authentication/migrations/0007_friendrequest.py b/backend/authentication/migrations/0007_friendrequest.py new file mode 100644 index 000000000..2a4e89333 --- /dev/null +++ b/backend/authentication/migrations/0007_friendrequest.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0 on 2023-12-22 13:23 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0006_alter_user_profile_picture'), + ] + + operations = [ + migrations.CreateModel( + name='FriendRequest', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('rejected', 'Rejected')], default='pending', max_length=10)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_requests_sent', to=settings.AUTH_USER_MODEL)), + ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_requests_received', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/authentication/models.py b/backend/authentication/models.py index 71a836239..0ef6b74a0 100644 --- a/backend/authentication/models.py +++ b/backend/authentication/models.py @@ -1,3 +1,44 @@ from django.db import models - +from django.contrib.auth.models import AbstractUser +from .utils import user_profile_picture_path # Create your models here. + +class User(AbstractUser): + + profile_picture = models.ImageField(upload_to=user_profile_picture_path, null=True, blank=True, default='default_profile_picture.PNG') + total_matches = models.PositiveIntegerField(default=0) + wins = models.PositiveIntegerField(default=0) + losses = models.PositiveIntegerField(default=0) + title = models.CharField(max_length=100, null=True, blank=True) + + def get_friends(self) -> list: + accepted_requests = FriendRequest.objects.filter( + models.Q(from_user=self, status='accepted') | models.Q(to_user=self, status='accepted') + ) + friends = [request.to_user if request.from_user == self else request.from_user for request in accepted_requests] + return friends + + def get_received_friend_requests(self) -> list: + received_requests = FriendRequest.objects.filter(to_user=self, status='pending') + users = [request.from_user for request in received_requests] + return users + + + + def __str__(self) -> str: + return "Custom User: " + super().__str__() + ' . Total matches: ' + str(self.total_matches) + ' . Wins: ' + str(self.wins) + ' . Losses: ' + str(self.losses) + +class FriendRequest(models.Model): + STATUS_CHOICES = ( + ('pending', 'Pending'), + ('accepted', 'Accepted'), + ('rejected', 'Rejected'), + ) + + from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friend_requests_sent') + to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friend_requests_received') + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending') + timestamp = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.from_user} to {self.to_user} ({self.status})" \ No newline at end of file diff --git a/backend/authentication/templates/base.html b/backend/authentication/templates/base.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/authentication/templates/home.html b/backend/authentication/templates/home.html index 077a65aeb..2d075080c 100644 --- a/backend/authentication/templates/home.html +++ b/backend/authentication/templates/home.html @@ -2,32 +2,45 @@ - - - Welcome to Ping Pong World - - + + + Welcome to Ping Pong World + + -
-

Ping Pong Home Page

-

The Ultimate Ping Pong Experience!

+
+

Ping Pong Home Page

+

The Ultimate Ping Pong Experience!

-
- Play - Now - Learn More + +

+ Welcome, {{ username }}! +

+

+ Email: {{ email }} +

- - Logout -
-
+
+ Play + Now + Learn More - - - + + Logout +
+ + + Users List +
+ + + + diff --git a/backend/authentication/templates/login.html b/backend/authentication/templates/login.html index ad6be61d4..c9a99b33e 100644 --- a/backend/authentication/templates/login.html +++ b/backend/authentication/templates/login.html @@ -16,15 +16,19 @@

Login Page

+ {% csrf_token %}
- - + +
+
- +
@@ -34,8 +38,15 @@

Login Page

Or


+ {% csrf_token %}
+ + {% if error_message %} + + {% endif %}
diff --git a/backend/authentication/templates/test.html b/backend/authentication/templates/test.html new file mode 100644 index 000000000..8a32a3b1d --- /dev/null +++ b/backend/authentication/templates/test.html @@ -0,0 +1,26 @@ + + + + + + + Test Page + + + + +
+

Test Page

+ +

+ {% if user %} + User created or already exists. User: {{ user.username }} + {% else %} + An error occurred while creating the user. + {% endif %} +

+
+ + + + diff --git a/backend/authentication/templates/users_list.html b/backend/authentication/templates/users_list.html new file mode 100644 index 000000000..80a6ba13d --- /dev/null +++ b/backend/authentication/templates/users_list.html @@ -0,0 +1,35 @@ + + + + + + + + User List + + + + +
+
+ {% for user in users %} +
+

{{ user.username }}

+ User profile picture +

{{ user.email }}

+

Total Matches: {{ user.total_matches }}

+

Wins: {{ user.wins }}

+

Losses: {{ user.losses }}

+

Title: {{ user.title }}

+ +
+ {% endfor %} + +
+
+ + + + + + \ No newline at end of file diff --git a/backend/authentication/urls.py b/backend/authentication/urls.py index 9efbd69d4..0f495d298 100644 --- a/backend/authentication/urls.py +++ b/backend/authentication/urls.py @@ -1,7 +1,9 @@ from django.urls import path from . import views + # Create different uri roots to connect them to a view + urlpatterns = [ path("", views.home, name="home"), path("login/", views.my_login, name="login"), @@ -9,5 +11,8 @@ path("auth/", views.auth, name="auth"), path("auth_callback/", views.auth_callback, name="auth_callback"), path("register/", views.register, name="register"), -] + path("users_list/", views.users_list, name="users_list"), + path('send_friend_request//', views.send_friend_request, name='send_friend_request'), + path("test/", views.test, name="test"), +] diff --git a/backend/authentication/utils.py b/backend/authentication/utils.py new file mode 100644 index 000000000..6d80a87b4 --- /dev/null +++ b/backend/authentication/utils.py @@ -0,0 +1,5 @@ +def user_profile_picture_path(instance, filename): + # Assuming 'instance' is a User instance + return f'authentication/images/{instance.username}/{filename}' + + diff --git a/backend/authentication/views.py b/backend/authentication/views.py index 48cb145ca..90c3671a9 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -1,23 +1,67 @@ from django.shortcuts import render, HttpResponse, redirect from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.conf import settings import requests import os import urllib +from .models import User +from django.core.files.base import ContentFile +from django.contrib.auth import get_user_model +from django.shortcuts import get_object_or_404 +from .models import FriendRequest + + +# ----------------------------------------––--------------- HOME FUNCTION @login_required(login_url='/login/') def home(request): - return render(request, "home.html") + context = { + 'username': request.user.username, + 'email': request.user.email, + } + print(request.user) + return render(request, 'home.html', context) + +# ----------------------------------------––--------------- LOGIN FUNCTION # don't name it 'login' because it will conflict with the built-in login function def my_login(request): if request.user.is_authenticated: return redirect("home") - return render(request, "login.html") + if request.method == 'POST': + username = request.POST.get('username') + password = request.POST.get('password') + + + # Authenticate the user + user = authenticate(request, username=username, password=password) + print(f'Username: {username}') + print(f'Password: {password}') + print(f'User: {user}') + + if user is not None: + # Authentication successful, log in the user + login(request, user) + return redirect('home') # Redirect to the home page or another desired destination + else: + # Authentication failed, display an error message + error_message = 'Invalid username or password. Please try again.' + return render(request, 'login.html', {'error_message': error_message}) + # Render the login form + return render(request, 'login.html') + +# ----------------------------------------––--------------- LOGOUT FUNCTION +def my_logout(request): + # Log the user out using Django's built-in logout function + logout(request) + # Redirect the user to the login page + return redirect('login') # Replace 'login' with the actual name or URL of your login view + + +# ----------------------------------------––--------------- AUTHENTICATION CALLBACK FUNCTIONS def auth_callback(request): if request.method == "GET": @@ -40,36 +84,45 @@ def auth_callback(request): # ---------------------------------------––---------------- GET USER DATA FROM 42 SERVER RESPONSE # id will be implemented later, once we have a modified User model # id = user_response.json()["id"] + # Extract user information from the response username = user_response.json()["login"] first_name = user_response.json()["first_name"] last_name = user_response.json()["last_name"] email = user_response.json()["email"] - # picture will be implemented later, once we have a modified User model - # picture = user_response.json()["image"]["link"] + picture_url = user_response.json()["image"]["versions"]["medium"] + titles = user_response.json().get("titles", []) + title = "" + if titles: + title = titles[0].get("name", "") + title = str(title).split()[0] if title else "" - # -----------------------------------------––-------------- CREATE USER TO AUTHENTICATE AND LOGIN - # user is a Model variable - # created is a boolean variable user, created = User.objects.get_or_create( - username=username, + username=username, defaults={ - 'username' : username, - 'first_name': first_name, - 'last_name': last_name, - 'email': email + 'username': username, + 'first_name': first_name, + 'last_name': last_name, + 'email': email, + 'title': title, } ) if created: print("\t\t\tNew user has been added!!!") + response = requests.get(picture_url) + if response.status_code == 200: + user.profile_picture.save(f"{username}_profile_picture.jpg", ContentFile(response.content), save=True) else: print("\t\t\tUser already exists!!!") + + print(user) login(request, user) # ---------------------------------------––---------------- REDIRECT TO HOME PAGE return redirect("home") return HttpResponse("Auth callback Error, bad token maybe!!") +# ----------------------------------------––--------------- AUTHENTICATION FUNCTIONS def auth(request): auth_url = "https://api.intra.42.fr/oauth/authorize" params = { @@ -79,13 +132,8 @@ def auth(request): } return redirect(f"{auth_url}?{urllib.parse.urlencode(params)}") -def my_logout(request): - # Log the user out using Django's built-in logout function - logout(request) - - # Redirect the user to the login page - return redirect('login') # Replace 'login' with the actual name or URL of your login view +# ----------------------------------------––--------------- REGISTER FUNCTION def register(request): if request.user.is_authenticated: return redirect("home") @@ -101,7 +149,7 @@ def register(request): user = User.objects.create_user(username=username, email=email, password=password, first_name=first_name, last_name=last_name) # Log the user in - # login(request, user) + login(request, user) print("\t\t\tNew user has been added!!!") print(f'Username: {user.username}') print(f'Email: {user.email}') @@ -111,3 +159,56 @@ def register(request): # Redirect to a success page or home return redirect('home') # Change 'home' to the actual name of your home page or dashboard return render(request, 'register.html') + + +# ----------------------------------------––--------------- TEST FUNCTION +def test(request): + # User data + username = 'Jason' + password = 'Bourne' + + # Create or get the user + user, created = User.objects.get_or_create(username=username) + user.set_password(password) + user.save() + + # Print messages for demonstration purposes + if created: + print("User created") + else: + print("User already exists") + + print(user) + + # Pass the user object to the template + return render(request, 'test.html', {'user': user}) + + +# ----------------------------------------––--------------- USERS LIST FUNCTION +def users_list(request): + # Get all users from the database + users = User.objects.all() + + # Pass the users to the template + return render(request, 'users_list.html', {'users': users}) + + +def has_friend_request_sent(from_user, to_user): + return FriendRequest.objects.filter(from_user=from_user, to_user=to_user, status='pending').exists() + +def send_friend_request(request, to_user_id): + # Get the 'to_user' object using the 'to_user_id' + to_user = get_object_or_404(User, pk=to_user_id) + + # Check if a friend request already exists or if the users are the same + existing_request = FriendRequest.objects.filter(from_user=request.user, to_user=to_user).first() + if existing_request or request.user == to_user: + # Handle the case where a request already exists or trying to send a request to oneself + # You may want to display an error message or redirect to a different page + pass + else: + # Create a new friend request + friend_request = FriendRequest(from_user=request.user, to_user=to_user) + friend_request.save() + + return redirect('users_list') # Redirect to the user's profile page or any other desired page \ No newline at end of file diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 18c7b69f1..4f7d8b3eb 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -11,6 +11,7 @@ """ from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -20,7 +21,7 @@ # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-ej^v-=x0xri%*-u@r0(+)pbc+onq+^rupromej&wxag1esu(f4' +SECRET_KEY = 'django-insecure-#klz0g4gt37#*8aou2@a&u-k)rofekot&_7$o8ee1^=er*=1&0' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -40,8 +41,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', - 'authentication', - # 'leaderboard', + 'authentication', ] MIDDLEWARE = [ @@ -78,6 +78,15 @@ # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases +# THIS IS THE DEFAULT DATABASE CONFIGURATION +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', +# } +# } + +# THIS IS THE DATABASE CONFIGURATION FOR THE DOCKER CONTAINER DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', @@ -89,7 +98,6 @@ } } - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -130,3 +138,12 @@ # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# For the custom user model to work we need to include this line +AUTH_USER_MODEL = 'authentication.User' + +STATIC_URL = '/static/' +STATICFILES_DIRS = [BASE_DIR / 'static'] + +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' \ No newline at end of file diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 9dca6e82d..d621ec5bf 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -16,10 +16,10 @@ """ from django.contrib import admin from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ - path('', include('authentication.urls')), - # path('leaderboard/', include('leaderboard.urls')), - # path('home/', include('authentication.urls')), path('admin/', admin.site.urls), -] + path('', include('authentication.urls')), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/backend/chat/__init__.py b/backend/chat/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/chat/admin.py b/backend/chat/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/backend/chat/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/backend/chat/apps.py b/backend/chat/apps.py deleted file mode 100644 index 2fe899ad4..000000000 --- a/backend/chat/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class ChatConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'chat' diff --git a/backend/chat/migrations/__init__.py b/backend/chat/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/chat/models.py b/backend/chat/models.py deleted file mode 100644 index 71a836239..000000000 --- a/backend/chat/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/backend/chat/tests.py b/backend/chat/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/backend/chat/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/chat/views.py b/backend/chat/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/backend/chat/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/backend/game/__init__.py b/backend/game/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/game/admin.py b/backend/game/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/backend/game/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/backend/game/apps.py b/backend/game/apps.py deleted file mode 100644 index 8ad49cb8e..000000000 --- a/backend/game/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class GameConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'game' diff --git a/backend/game/migrations/__init__.py b/backend/game/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/game/models.py b/backend/game/models.py deleted file mode 100644 index 71a836239..000000000 --- a/backend/game/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/backend/game/tests.py b/backend/game/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/backend/game/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/game/views.py b/backend/game/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/backend/game/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/backend/leaderboard/__init__.py b/backend/leaderboard/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/leaderboard/admin.py b/backend/leaderboard/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/backend/leaderboard/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/backend/leaderboard/apps.py b/backend/leaderboard/apps.py deleted file mode 100644 index 590a45294..000000000 --- a/backend/leaderboard/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class LeaderboardConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'leaderboard' diff --git a/backend/leaderboard/migrations/0001_initial.py b/backend/leaderboard/migrations/0001_initial.py deleted file mode 100644 index b53e51405..000000000 --- a/backend/leaderboard/migrations/0001_initial.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.0 on 2023-12-13 10:59 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('authentication', '0002_alter_user_id'), - ] - - operations = [ - migrations.CreateModel( - name='Friendship', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friendship_receiver', to='authentication.user')), - ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friendship_sender', to='authentication.user')), - ], - options={ - 'unique_together': {('sender', 'receiver')}, - }, - ), - ] diff --git a/backend/leaderboard/migrations/0002_alter_friendship_unique_together_friendship_accepted_and_more.py b/backend/leaderboard/migrations/0002_alter_friendship_unique_together_friendship_accepted_and_more.py deleted file mode 100644 index 35457f2b6..000000000 --- a/backend/leaderboard/migrations/0002_alter_friendship_unique_together_friendship_accepted_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.0 on 2023-12-13 12:27 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0002_alter_user_id'), - # ('leaderboard', '0001_initial'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='friendship', - unique_together=set(), - ), - migrations.AddField( - model_name='friendship', - name='accepted', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='friendship', - name='receiver', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_friend_requests', to='authentication.user'), - ), - migrations.AlterField( - model_name='friendship', - name='sender', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_friend_requests', to='authentication.user'), - ), - migrations.RemoveField( - model_name='friendship', - name='created_at', - ), - ] diff --git a/backend/leaderboard/migrations/0003_delete_friendship.py b/backend/leaderboard/migrations/0003_delete_friendship.py deleted file mode 100644 index 32b51c22a..000000000 --- a/backend/leaderboard/migrations/0003_delete_friendship.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 5.0 on 2023-12-15 09:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('leaderboard', '0002_alter_friendship_unique_together_friendship_accepted_and_more'), - ] - - operations = [ - migrations.DeleteModel( - name='Friendship', - ), - ] diff --git a/backend/leaderboard/migrations/__init__.py b/backend/leaderboard/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/leaderboard/models.py b/backend/leaderboard/models.py deleted file mode 100644 index 370851e92..000000000 --- a/backend/leaderboard/models.py +++ /dev/null @@ -1,11 +0,0 @@ -# from django.db import models -# from authentication.models import User - - -# class Friendship(models.Model): -# sender = models.ForeignKey(User, related_name='sent_friend_requests', on_delete=models.CASCADE) -# receiver = models.ForeignKey(User, related_name='received_friend_requests', on_delete=models.CASCADE) -# accepted = models.BooleanField(default=False) - -# def __str__(self): -# return f"{self.sender.username} - {self.receiver.username} ({'Accepted' if self.accepted else 'Pending'})" \ No newline at end of file diff --git a/backend/leaderboard/templates/leaderboard/leaderboard.html b/backend/leaderboard/templates/leaderboard/leaderboard.html deleted file mode 100644 index 3064a24ba..000000000 --- a/backend/leaderboard/templates/leaderboard/leaderboard.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Document - - - -

User List

- - - - - \ No newline at end of file diff --git a/backend/leaderboard/tests.py b/backend/leaderboard/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/backend/leaderboard/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/leaderboard/urls.py b/backend/leaderboard/urls.py deleted file mode 100644 index 242731ebe..000000000 --- a/backend/leaderboard/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -# users_list/urls.py -from django.urls import path -from . import views - -urlpatterns = [ - # path('leaderboard/', views.leaderboard, name='leaderboard'), - # path('send_friend_request/', views.send_friend_request, name='send_friend_request'), - # Add other URL patterns as needed -] \ No newline at end of file diff --git a/backend/leaderboard/views.py b/backend/leaderboard/views.py deleted file mode 100644 index 1b5a5df0e..000000000 --- a/backend/leaderboard/views.py +++ /dev/null @@ -1,38 +0,0 @@ -# # users_list/views.py -# from django.shortcuts import render -# # from authentication.models import User - - -# def leaderboard(request): -# users = User.objects.all() -# # print("\t\t\t NUM OF USERS: ", end='') -# # print(len(users)) -# # print(users) -# # for user in users: -# # print(user) -# context = {'users' : users} -# return render(request, 'leaderboard/leaderboard.html', context) - - - -# # NOT READY YET -# def send_friend_request(request): -# if request.method == 'POST': -# sender_id = request.user.id # Assuming you have a logged-in user -# receiver_id = request.POST.get('receiver_id') - -# # Check if the friendship already exists -# existing_friendship = Friendship.objects.filter(sender_id=sender_id, receiver_id=receiver_id).first() - -# if existing_friendship: -# messages.error(request, 'Friend request already sent.') -# else: -# # Create a new friendship request -# friendship = Friendship(sender_id=sender_id, receiver_id=receiver_id) -# friendship.save() -# messages.success(request, 'Friend request sent successfully.') - -# return redirect('leaderboard') -# else: -# # Redirect to leaderboard page if the request method is not POST -# return redirect('leaderboard') \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index d022601ab..37162a163 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -7,3 +7,4 @@ psycopg2-binary==2.9.9 requests==2.31.0 sqlparse==0.4.4 urllib3==2.1.0 +Pillow==10.1.0 diff --git a/docker-compose.yaml b/docker-compose.yaml index fa01d105a..d3b61c3a8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,32 +15,29 @@ services: - 3000:3000 restart: unless-stopped command: npm start -# depends_on: -# - backend - -# backend: -# container_name: backend -# env_file: -# - ./.env -# build: -# context: . -# dockerfile: docker/backend/backend.Dockerfile -# image: transcendence_backend -# volumes: -# - ${PWD}:/app/ -# ports: -# - 8000:8000 -# restart: unless-stopped -# entrypoint: /app/docker/backend/backend.sh -# depends_on: -# - db -# # need this shit properly pls -# db: -# image: postgres -# environment: -# POSTGRES_DB: transcend_users_db -# POSTGRES_USER: transcend_user -# POSTGRES_PASSWORD: transcend_pwd -# ports: -# - "5432:5432" + # backend: + # container_name: backend + # env_file: + # - ./.env + # build: + # context: . + # dockerfile: docker/backend/backend.Dockerfile + # image: transcendence_backend + # volumes: + # - ${PWD}:/app/ + # ports: + # - 8000:8000 + # # restart: unless-stopped + # depends_on: + # - db + + # db: + # image: postgres + # container_name: database + # environment: + # POSTGRES_DB: transcend_users_db + # POSTGRES_USER: transcend_user + # POSTGRES_PASSWORD: transcend_pwd + # ports: + # - "5432:5432" diff --git a/docker/backend/backend.Dockerfile b/docker/backend/backend.Dockerfile index 4372685c7..6a2a08268 100644 --- a/docker/backend/backend.Dockerfile +++ b/docker/backend/backend.Dockerfile @@ -16,5 +16,5 @@ RUN chmod +x /app/backend.sh EXPOSE 8000 -# ENTRYPOINT ["tail", "-f", "/dev/null"] +ENTRYPOINT ["tail", "-f", "/dev/null"] ENTRYPOINT ["docker/backend/backend.sh"] \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 228a41d5f..983d87be6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1621,9 +1621,9 @@ } }, "@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==" + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==" }, "@formatjs/ecma402-abstract": { "version": "1.18.0", @@ -1632,13 +1632,6 @@ "requires": { "@formatjs/intl-localematcher": "0.5.2", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/fast-memoize": { @@ -1647,13 +1640,6 @@ "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", "requires": { "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/icu-messageformat-parser": { @@ -1664,13 +1650,6 @@ "@formatjs/ecma402-abstract": "1.18.0", "@formatjs/icu-skeleton-parser": "1.7.0", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/icu-skeleton-parser": { @@ -1680,13 +1659,6 @@ "requires": { "@formatjs/ecma402-abstract": "1.18.0", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/intl": { @@ -1701,13 +1673,6 @@ "@formatjs/intl-listformat": "7.5.3", "intl-messageformat": "10.5.8", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/intl-displaynames": { @@ -1718,13 +1683,6 @@ "@formatjs/ecma402-abstract": "1.18.0", "@formatjs/intl-localematcher": "0.5.2", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/intl-listformat": { @@ -1735,13 +1693,6 @@ "@formatjs/ecma402-abstract": "1.18.0", "@formatjs/intl-localematcher": "0.5.2", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@formatjs/intl-localematcher": { @@ -1750,13 +1701,6 @@ "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", "requires": { "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "@humanwhocodes/config-array": { @@ -1779,6 +1723,59 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2669,6 +2666,12 @@ "fastq": "^1.6.0" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -2686,9 +2689,9 @@ } }, "@remix-run/router": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz", - "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", + "integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==" }, "@rollup/plugin-babel": { "version": "5.3.1", @@ -2739,9 +2742,9 @@ } }, "@rushstack/eslint-patch": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.0.tgz", - "integrity": "sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz", + "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==" }, "@sinclair/typebox": { "version": "0.27.8", @@ -3002,9 +3005,9 @@ } }, "@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "requires": { "@babel/types": "^7.0.0" } @@ -3061,9 +3064,9 @@ } }, "@types/eslint": { - "version": "8.44.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.9.tgz", - "integrity": "sha512-6yBxcvwnnYoYT1Uk2d+jvIfsuP4mb2EdIxFnrPABj5a/838qe5bGkNLFOiipX4ULQ7XVQvTxOh7jO+BTAiqsEw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -3186,9 +3189,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "@types/node": { - "version": "20.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", - "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "requires": { "undici-types": "~5.26.4" } @@ -3222,9 +3225,9 @@ "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==" }, "@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" }, "@types/range-parser": { "version": "1.2.7", @@ -3242,9 +3245,9 @@ } }, "@types/react-dom": { - "version": "18.2.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", - "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", "requires": { "@types/react": "*" } @@ -4318,13 +4321,6 @@ "requires": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "camelcase": { @@ -4349,9 +4345,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001570", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", - "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==" + "version": "1.0.30001571", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz", + "integrity": "sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -5231,13 +5227,6 @@ "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "dotenv": { @@ -5255,6 +5244,11 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5269,9 +5263,9 @@ } }, "electron-to-chromium": { - "version": "1.4.611", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.611.tgz", - "integrity": "sha512-ZtRpDxrjHapOwxtv+nuth5ByB8clyn8crVynmRNGO3wG3LOp8RTcyZDqwaI6Ng6y8FCK2hVZmJoqwCskKbNMaw==" + "version": "1.4.616", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz", + "integrity": "sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==" }, "emittery": { "version": "0.8.1", @@ -5485,14 +5479,14 @@ } }, "eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5670,9 +5664,9 @@ } }, "eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "requires": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -5690,7 +5684,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { @@ -6105,9 +6099,9 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "requires": { "reusify": "^1.0.4" } @@ -6260,6 +6254,22 @@ "is-callable": "^1.1.3" } }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, "fork-ts-checker-webpack-plugin": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", @@ -6716,9 +6726,9 @@ } }, "html-webpack-plugin": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.4.tgz", - "integrity": "sha512-3wNSaVVxdxcu0jd4FpQFoICdqgxs4zIQQvj+2yQKFfBOnLETQ6X5CDWdeasuGlSsooFlMkEioWDTqBv1wvw5Iw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", "requires": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -6943,13 +6953,6 @@ "@formatjs/fast-memoize": "2.2.0", "@formatjs/icu-messageformat-parser": "2.7.3", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "ipaddr.js": { @@ -7315,6 +7318,15 @@ "set-function-name": "^2.0.1" } }, + "jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -9463,13 +9475,6 @@ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "requires": { "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "lru-cache": { @@ -9659,6 +9664,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, + "minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" + }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -9723,13 +9733,6 @@ "requires": { "lower-case": "^2.0.2", "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "node-forge": { @@ -9979,13 +9982,6 @@ "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "parent-module": { @@ -10024,13 +10020,6 @@ "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "path-exists": { @@ -10053,6 +10042,22 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==" + } + } + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -10516,9 +10521,9 @@ } }, "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", + "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", "requires": { "postcss-selector-parser": "^6.0.4" } @@ -11160,13 +11165,6 @@ "hoist-non-react-statics": "^3.3.2", "intl-messageformat": "10.5.8", "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } } }, "react-is": { @@ -11180,20 +11178,20 @@ "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, "react-router": { - "version": "6.20.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz", - "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz", + "integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==", "requires": { - "@remix-run/router": "1.13.1" + "@remix-run/router": "1.14.1" } }, "react-router-dom": { - "version": "6.20.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz", - "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz", + "integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==", "requires": { - "@remix-run/router": "1.13.1", - "react-router": "6.20.1" + "@remix-run/router": "1.14.1", + "react-router": "6.21.1" } }, "react-scripts": { @@ -11333,9 +11331,9 @@ } }, "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { "version": "0.15.2", @@ -12101,6 +12099,23 @@ } } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + } + } + }, "string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -12180,6 +12195,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -12223,35 +12246,50 @@ } }, "sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "requires": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "requires": { + "brace-expansion": "^2.0.1" } } } @@ -12355,9 +12393,9 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "tailwindcss": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz", - "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", + "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==", "requires": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -12551,9 +12589,9 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -12572,9 +12610,9 @@ } }, "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tsutils": { "version": "3.21.0", @@ -12582,6 +12620,13 @@ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "requires": { "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } } }, "type-check": { @@ -13094,9 +13139,9 @@ } }, "whatwg-fetch": { - "version": "3.6.19", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", - "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "whatwg-mimetype": { "version": "2.3.0", @@ -13450,6 +13495,16 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7bcdc1f28..406dc34fa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,8 +1,8 @@ { "name": "frontend", "version": "0.1.0", - "homepage": "https://zstenger93.github.io/Transcendence", "private": true, + "homepage": "https://zstenger93.github.io/Transcendence/", "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/frontend/src/App.js b/frontend/src/App.js index e276e1aae..121220183 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,15 +1,21 @@ -import React from "react"; -import { I18nextProvider } from "react-i18next"; +import React, { useEffect } from "react"; +import { I18nextProvider, useTranslation } from "react-i18next"; import Translation from "./components/Translation"; import Home from "./components/Home"; import Chat from "./components/Chat"; import Games from "./components/Games"; import About from "./components/About"; +import NotFound from "./components/404"; import PongAi from "./components/PongAi"; import Sidebar from "./components/Sidebar"; import Welcome from "./components/Welcome"; import Profile from "./components/Profile"; -import backgroundImage from "./images/bg0.png"; +import HomeBackground from "./images/bg0.png"; +import MortyBackground0 from "./images/morty0.png"; +import MortyBackground1 from "./images/morty1.png"; +import MortyBackground2 from "./images/morty2.png"; +import MortyBackground3 from "./images/morty3.png"; +import MortyBackground4 from "./images/morty4.png"; import Matchmaking from "./components/Matchmaking"; import OriginalPong from "./components/OriginalPong"; import ChoosePongMode from "./components/ChoosePongMode"; @@ -18,43 +24,126 @@ import { BrowserRouter as Router, Route, Routes, - Navigate, } from "react-router-dom"; +const PageWrapper = ({ children, image, showSidebar = true }) => { + return ( +
+ {showSidebar && } + {children} +
+ ); +}; + function App() { + const { i18n } = useTranslation(); const basename = process.env.NODE_ENV === 'production' ? '/Transcendence' : ''; + useEffect(() => { + const storedLanguage = localStorage.getItem('i18nextLng'); + if (storedLanguage && i18n.language !== storedLanguage) { + i18n.changeLanguage(storedLanguage); + } + }, [i18n]); + return ( - } /> - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js index 57c684b45..99994aac6 100644 --- a/frontend/src/App.test.js +++ b/frontend/src/App.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, prettyDOM, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent, prettyDOM, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import App from './App'; import { within } from '@testing-library/dom'; @@ -13,7 +13,7 @@ test('renders Sign In button', () => { console.log(prettyDOM(container)); - const signInButton = screen.getByText('Sign In'); + const signInButton = screen.getByText('Sign In via 42'); expect(signInButton).toBeInTheDocument(); }); @@ -21,7 +21,7 @@ test('renders Sign In button', () => { test('clicks on Sign In button and interacts with Sidebar', async () => { const { container } = render(); - const signInButton = screen.getByText('Sign In'); + const signInButton = screen.getByText('Sign In via 42'); userEvent.click(signInButton); await waitFor(() => screen.getByText('Logout')); @@ -29,13 +29,43 @@ test('clicks on Sign In button and interacts with Sidebar', async () => { const homeLink = screen.getByText('Home'); const chatLink = screen.getByText('Channels & Private Messages'); + const gamesLink = screen.getByText('Play & Watch Games'); + const profileLink = screen.getByText('Profile'); + const aboutLink = screen.getByText('About Us'); + const logoutLink = screen.getByText('Logout'); userEvent.click(homeLink); userEvent.click(chatLink); - console.log(prettyDOM(container, 50000)); - + userEvent.click(gamesLink); + userEvent.click(profileLink); + userEvent.click(aboutLink); + userEvent.click(logoutLink); }); +// test('async test for page data', async () => { +// const { container } = render(); + +// const signInButton = screen.getByText('Sign In via 42'); +// userEvent.click(signInButton); + +// await waitFor(() => screen.getByText('Logout')); +// await waitFor(() => screen.getByText('Welcome To')); +// }); + +// jest.mock('react-i18next', () => ({ +// useTranslation: () => ({ t: key => key }) +// })); + +// test('async test for page data', async () => { +// render(); + +// const signInButton = screen.getByText('Sign In via 42'); +// userEvent.click(signInButton); + +// await waitFor(() => screen.getByText('Logout')); +// await waitFor(() => screen.getByText('Welcome To')); +// }); + // test('renders chat page', () => { // render(); // const chatElement = screen.getByText('Chat'); diff --git a/frontend/src/components/404.js b/frontend/src/components/404.js new file mode 100644 index 000000000..3566b46d1 --- /dev/null +++ b/frontend/src/components/404.js @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import NotFoundVideo from '../images/404.mp4'; +import { useTranslation } from 'react-i18next'; + +const NotFound = ({ currentLanguage }) => { + const navigate = useNavigate(); + const [counter, setCounter] = useState(10); + const { t, i18n } = useTranslation(); + + useEffect(() => { + const storedLanguage = localStorage.getItem('myAppLanguage'); + if (storedLanguage && i18n.language !== storedLanguage) { + i18n.changeLanguage(storedLanguage); + } + + if (counter > 0) { + const timer = setTimeout(() => { + setCounter(counter - 1); + }, 1150); + return () => clearTimeout(timer); + } else { + navigate('/'); + } + }, [counter, navigate, i18n, currentLanguage]); + + return ( +
+ +
+

+ 404 !@#$ +

+

+ {t('Your portal gun must be busted ...')} +

+
+
+ {t('Your curious ass will be redirected in')}
+ {counter} +
+
+ ); +}; + +export default NotFound; \ No newline at end of file diff --git a/frontend/src/components/About.js b/frontend/src/components/About.js index ceff5c026..a3cca3747 100644 --- a/frontend/src/components/About.js +++ b/frontend/src/components/About.js @@ -3,7 +3,6 @@ import Slider from "react-slick"; import { useTranslation } from "react-i18next"; import "slick-carousel/slick/slick.css"; import "slick-carousel/slick/slick-theme.css"; -import backgroundImage from "../images/bg0.png"; function About() { const { t } = useTranslation(); @@ -57,7 +56,6 @@ function About() {
{teamMembers.map((member, index) => ( diff --git a/frontend/src/components/Chat.js b/frontend/src/components/Chat.js index 4de2ce62d..454165277 100644 --- a/frontend/src/components/Chat.js +++ b/frontend/src/components/Chat.js @@ -106,7 +106,7 @@ function Chat() { @@ -184,7 +184,7 @@ function Chat() { diff --git a/frontend/src/components/ChoosePongMode.js b/frontend/src/components/ChoosePongMode.js index 136395264..e36396781 100644 --- a/frontend/src/components/ChoosePongMode.js +++ b/frontend/src/components/ChoosePongMode.js @@ -12,40 +12,40 @@ function ChoosePongMode() { {t("Original Pong")} {t("Multiplayer")} {t("Modded Pong")} {t("Pong against AI")} {t("3D Pong")} @@ -53,7 +53,8 @@ function ChoosePongMode() { diff --git a/frontend/src/components/Games.js b/frontend/src/components/Games.js index 25b4038ff..8fba0e286 100644 --- a/frontend/src/components/Games.js +++ b/frontend/src/components/Games.js @@ -1,5 +1,4 @@ import React from "react"; -import backgroundImage from "../images/bg0.png"; import { Link } from "react-router-dom"; function Games() { @@ -10,7 +9,7 @@ function Games() { "https://www.youtube.com/embed/dQw4w9WgXcQ?controls=0&showinfo=0&rel=0&autoplay=1&mute=1&loop=1&playlist=dQw4w9WgXcQ", }, { - name: "BRAINFUCK", + name: "Emotional Damage", video: "https://www.youtube.com/embed/dQw4w9WgXcQ?controls=0&showinfo=0&rel=0&autoplay=1&mute=1&loop=1&playlist=dQw4w9WgXcQ", }, @@ -19,8 +18,7 @@ function Games() { return (
{tiles.map((tile, index) => ( @@ -28,7 +26,8 @@ function Games() { key={index} to="/choosepongmode" className="relative bg-white p-4 rounded shadow - border-black border-2 text-black overflow-hidden m-2" + border-black border-2 text-black font-nosifer + overflow-hidden m-2" style={{ width: "30vw", height: "16.875vw" }} >
{tile.name} diff --git a/frontend/src/components/Home.js b/frontend/src/components/Home.js index 3bf6f0d22..4d37c3162 100644 --- a/frontend/src/components/Home.js +++ b/frontend/src/components/Home.js @@ -1,12 +1,10 @@ import React from "react"; -import backgroundImage from "../images/bg0.png"; import WelcomeMessage from "./WelcomeMessage"; function Home() { return (
diff --git a/frontend/src/components/Matchmaking.js b/frontend/src/components/Matchmaking.js index cb377cf90..b8b63d48e 100644 --- a/frontend/src/components/Matchmaking.js +++ b/frontend/src/components/Matchmaking.js @@ -1,12 +1,10 @@ import React from "react"; -import backgroundImage from "../images/bg0.png"; function Matchmaking() { return (
); } diff --git a/frontend/src/components/Profile.js b/frontend/src/components/Profile.js index febc61e43..45d4796e9 100644 --- a/frontend/src/components/Profile.js +++ b/frontend/src/components/Profile.js @@ -278,6 +278,7 @@ function Profile() {
diff --git a/frontend/src/components/Translation.js b/frontend/src/components/Translation.js index c8adf151c..177962045 100644 --- a/frontend/src/components/Translation.js +++ b/frontend/src/components/Translation.js @@ -20,11 +20,11 @@ i18n.use(initReactI18next).init({ de: { translation: deTranslations, }, - jp: { - translation: jpTranslations, - }, + jp: { + translation: jpTranslations, + }, }, - lng: "en", + lng: localStorage.getItem('i18nextLng') || "en", fallbackLng: "en", interpolation: { escapeValue: false, diff --git a/frontend/src/components/Welcome.js b/frontend/src/components/Welcome.js index fb5229e6a..f1d8ab415 100644 --- a/frontend/src/components/Welcome.js +++ b/frontend/src/components/Welcome.js @@ -1,33 +1,147 @@ -import React from "react"; +import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import backgroundImage from "../images/bg0.png"; +import { HiMiniLanguage } from "react-icons/hi2"; + +function LanguageButton() { + const { t, i18n } = useTranslation(); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [currentLanguage, setCurrentLanguage] = useState(i18n.language); + + const changeLanguage = (lang) => { + i18n.changeLanguage(lang); + setCurrentLanguage(lang); + setDropdownOpen(false); + }; + + const languages = [ + { value: "en", label: "EN" }, + { value: "fr", label: "FR" }, + { value: "hu", label: "HU" }, + { value: "de", label: "DE" }, + { value: "jp", label: "JP" }, + ]; + + return ( +
+ + {dropdownOpen && ( +
+ {languages.map((lang) => ( + + ))} +
+ )} +
+ ); +} function Welcome() { const { t } = useTranslation(); const navigate = useNavigate(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showFields, setShowFields] = useState(false); - const redirectToHome = () => { - navigate("/home"); + + const redirectToHome = (isJoinButton) => { + if (isJoinButton && (!email || !password)) { + alert("Please enter email and password"); + } else { + navigate("/home"); + } }; + return (
-
+ +
+ {showFields && ( + <> + setEmail(e.target.value)} + placeholder={t("Email")} + onKeyPress={(e) => e.key === 'Enter' && redirectToHome()} + className="mb-2 mt-4 bg-gray-900 bg-opacity-60 text-white + rounded text-center border-b-2 border-r-2 border-purple-600" + autocomplete="new-email" + /> + setPassword(e.target.value)} + placeholder={t("Password")} + onKeyPress={(e) => e.key === 'Enter' && redirectToHome()} + className="mb-4 bg-gray-900 bg-opacity-60 text-white + rounded text-center border-b-2 border-r-2 border-purple-600" + autocomplete="new-password" + /> + + + )} + +
+
); } -export default Welcome; +export default Welcome; \ No newline at end of file diff --git a/frontend/src/components/WelcomeMessage.js b/frontend/src/components/WelcomeMessage.js index 5d0f05a04..e6346fc2f 100644 --- a/frontend/src/components/WelcomeMessage.js +++ b/frontend/src/components/WelcomeMessage.js @@ -5,7 +5,7 @@ function WelcomeMessage() { const { t } = useTranslation(); return (