diff --git a/.gitignore b/.gitignore
index 278b124..6a00f43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@
/coverage
# production
-/build
+/frontend/build
# misc
.DS_Store
@@ -22,4 +22,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
/backend/auth/auth/secrets.py
-/.env
+/frontend/.env
+/.idea
diff --git a/Pipfile b/Pipfile
index 9dadf7f..4bed272 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,6 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
+config = "*"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index 52ceab7..a2caf4f 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "5dad4df0acefd6ab931894470861025699c93b6eb981f0cdf8114665e13eb81e"
+ "sha256": "908f1fc3843d09a4e48db9ef70bc80ca36abde951462d4ed42d52deb6f7e1723"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,8 @@
}
]
},
- "default": {},
+ "default": {
+
+ },
"develop": {}
}
diff --git a/backend/auth/accounts/email.py b/backend/auth/accounts/email.py
new file mode 100644
index 0000000..10d1d18
--- /dev/null
+++ b/backend/auth/accounts/email.py
@@ -0,0 +1,22 @@
+from djoser import email
+from djoser import utils
+from djoser.conf import settings
+from django.contrib.auth.tokens import default_token_generator
+
+
+class ActivationEmail(email.ActivationEmail):
+ template_name = 'accounts/ActivationEmail.html'
+
+ def get_context_data(self):
+ # ActivationEmail can be deleted
+ context = super().get_context_data()
+
+ user = context.get("user")
+ context["uid"] = utils.encode_uid(user.pk)
+ context["token"] = default_token_generator.make_token(user)
+ context["url"] = settings.ACTIVATION_URL.format(**context)
+ return context
+
+
+class ConfirmationEmail(email.ConfirmationEmail):
+ template_name = 'accounts/ConfirmationEmail.html'
diff --git a/backend/oauth/oauth/__init__.py b/backend/auth/accounts/management/__init__.py
similarity index 100%
rename from backend/oauth/oauth/__init__.py
rename to backend/auth/accounts/management/__init__.py
diff --git a/backend/oauth/oauth/views.py b/backend/auth/accounts/management/commands/__init__.py
similarity index 100%
rename from backend/oauth/oauth/views.py
rename to backend/auth/accounts/management/commands/__init__.py
diff --git a/backend/auth/accounts/management/commands/init_rbac.py b/backend/auth/accounts/management/commands/init_rbac.py
new file mode 100644
index 0000000..2c63591
--- /dev/null
+++ b/backend/auth/accounts/management/commands/init_rbac.py
@@ -0,0 +1,39 @@
+from django.core.management.base import BaseCommand
+from accounts.models import Module, Permission, Role
+
+
+class Command(BaseCommand):
+ help = 'Initialize RBAC system with modules, permissions, and roles'
+
+ def handle(self, *args, **kwargs):
+ self.create_modules()
+ self.create_permissions()
+ self.create_roles()
+ self.stdout.write(self.style.SUCCESS('Successfully initialized RBAC system'))
+
+ def create_modules(self):
+ self.assets_module = Module.objects.get_or_create(name="Assets")[0]
+ self.comments_module = Module.objects.get_or_create(name="Comments")[0]
+ self.users_module = Module.objects.get_or_create(name="Users")[0]
+
+ def create_permissions(self):
+ self.create_assets = Permission.objects.get_or_create(name="Create", module=self.assets_module)[0]
+ self.read_assets = Permission.objects.get_or_create(name="Read", module=self.assets_module)[0]
+ self.update_assets = Permission.objects.get_or_create(name="Update", module=self.assets_module)[0]
+ self.delete_assets = Permission.objects.get_or_create(name="Delete", module=self.assets_module)[0]
+
+ self.read_comments = Permission.objects.get_or_create(name="Read", module=self.comments_module)[0]
+ self.create_comments = Permission.objects.get_or_create(name="Create", module=self.comments_module)[0]
+
+ self.read_users = Permission.objects.get_or_create(name="Read", module=self.users_module)[0]
+
+ def create_roles(self):
+ user_role = Role.objects.get_or_create(name="User")[0]
+ user_role.permissions.add(self.read_assets, self.read_comments, self.create_comments)
+
+ creator_role = Role.objects.get_or_create(name="Creator")[0]
+ creator_role.permissions.add(self.create_assets, self.read_assets, self.update_assets, self.delete_assets,
+ self.read_comments, self.create_comments)
+
+ organization_role = Role.objects.get_or_create(name="Organization")[0]
+ # Assign permissions based on special access features
diff --git a/backend/auth/accounts/migrations/0001_initial.py b/backend/auth/accounts/migrations/0001_initial.py
index 080e399..8d7756e 100644
--- a/backend/auth/accounts/migrations/0001_initial.py
+++ b/backend/auth/accounts/migrations/0001_initial.py
@@ -1,40 +1,73 @@
-# Generated by Django 4.1.10 on 2024-01-01 13:26
+# Generated by Django 4.1.10 on 2024-06-10 17:21
from django.db import migrations, models
+import uuid
class Migration(migrations.Migration):
-
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='User',
+ name="User",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('password', models.CharField(max_length=128, verbose_name='password')),
- ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
- ('email', models.EmailField(max_length=255, unique=True, verbose_name='Email')),
- ('first_name', models.CharField(max_length=200)),
- ('last_name', models.CharField(max_length=200)),
- ('age', models.PositiveIntegerField()),
- ('gender', models.CharField(max_length=200)),
- ('address', models.CharField(max_length=600)),
- ('preferred_lang', models.CharField(max_length=200)),
- ('company', models.CharField(max_length=200)),
- ('job_title', models.CharField(max_length=200)),
- ('industry', models.CharField(max_length=400)),
- ('experience', models.IntegerField()),
- ('is_active', models.BooleanField(default=True)),
- ('is_admin', models.BooleanField(default=False)),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
+ ("password", models.CharField(max_length=128, verbose_name="password")),
+ (
+ "last_login",
+ models.DateTimeField(
+ blank=True, null=True, verbose_name="last login"
+ ),
+ ),
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ primary_key=True,
+ serialize=False,
+ unique=True,
+ ),
+ ),
+ (
+ "email",
+ models.EmailField(
+ max_length=255, unique=True, verbose_name="Email"
+ ),
+ ),
+ ("first_name", models.CharField(max_length=200)),
+ ("last_name", models.CharField(max_length=200)),
+ ("date_of_birth", models.DateField()),
+ ("gender", models.CharField(max_length=200)),
+ ("address", models.CharField(max_length=600)),
+ ("preferred_lang", models.CharField(max_length=200)),
+ ("company", models.CharField(max_length=200)),
+ ("job_title", models.CharField(max_length=200)),
+ ("industry", models.CharField(max_length=400)),
+ ("experience", models.IntegerField()),
+ ("is_active", models.BooleanField(default=True)),
+ ("is_admin", models.BooleanField(default=False)),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
],
options={
- 'abstract': False,
+ "abstract": False,
},
),
+ migrations.CreateModel(
+ name="Module",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=50)),
+ ],
+ ),
]
diff --git a/backend/auth/accounts/migrations/0002_userpreferences.py b/backend/auth/accounts/migrations/0002_userpreferences.py
new file mode 100644
index 0000000..edeb405
--- /dev/null
+++ b/backend/auth/accounts/migrations/0002_userpreferences.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.1.10 on 2024-06-15 08:21
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("accounts", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="UserPreferences",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("dark_mode", models.BooleanField(default=False)),
+ (
+ "user",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/backend/auth/accounts/migrations/0003_permission_role_userrole.py b/backend/auth/accounts/migrations/0003_permission_role_userrole.py
new file mode 100644
index 0000000..e35dfb7
--- /dev/null
+++ b/backend/auth/accounts/migrations/0003_permission_role_userrole.py
@@ -0,0 +1,81 @@
+# Generated by Django 4.1.10 on 2024-06-15 12:51
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("accounts", "0002_userpreferences"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Permission",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=50)),
+ (
+ "module",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="accounts.module",
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="Role",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=50)),
+ ("permissions", models.ManyToManyField(to="accounts.permission")),
+ ],
+ ),
+ migrations.CreateModel(
+ name="UserRole",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "role",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="accounts.role",
+ ),
+ ),
+ (
+ "user",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/backend/auth/accounts/models.py b/backend/auth/accounts/models.py
index 84c4a4f..00c7a2d 100644
--- a/backend/auth/accounts/models.py
+++ b/backend/auth/accounts/models.py
@@ -1,23 +1,25 @@
-from django.db import models
+import uuid
+
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
+from django.db import models
# Custom User Manager
class UserManager(BaseUserManager):
def create_user(
- self,
- email,
- first_name,
- last_name,
- age,
- gender,
- address,
- preferred_lang,
- company,
- job_title,
- industry,
- experience,
- password=None,
+ self,
+ email,
+ first_name,
+ last_name,
+ date_of_birth,
+ gender,
+ address,
+ preferred_lang,
+ company,
+ job_title,
+ industry,
+ experience,
+ password=None,
):
"""
Creates and saves a User with the given email, name, tc and password.
@@ -29,7 +31,7 @@ def create_user(
email=self.normalize_email(email),
first_name=first_name,
last_name=last_name,
- age=age,
+ date_of_birth=date_of_birth,
gender=gender,
address=address,
preferred_lang=preferred_lang,
@@ -44,19 +46,19 @@ def create_user(
return user
def create_superuser(
- self,
- email,
- first_name,
- last_name,
- age,
- gender,
- address,
- preferred_lang,
- company,
- job_title,
- industry,
- experience,
- password=None,
+ self,
+ email,
+ first_name,
+ last_name,
+ date_of_birth,
+ gender,
+ address,
+ preferred_lang,
+ company,
+ job_title,
+ industry,
+ experience,
+ password=None,
):
"""
Creates and saves a superuser with the given email, name, tc and password.
@@ -66,7 +68,7 @@ def create_superuser(
password=password,
first_name=first_name,
last_name=last_name,
- age=age,
+ date_of_birth=date_of_birth,
gender=gender,
address=address,
preferred_lang=preferred_lang,
@@ -82,6 +84,12 @@ def create_superuser(
# Custom User Model
class User(AbstractBaseUser):
+ id = models.UUIDField(
+ primary_key=True,
+ default=uuid.uuid4,
+ editable=False,
+ unique=True
+ )
email = models.EmailField(
verbose_name="Email",
max_length=255,
@@ -89,7 +97,7 @@ class User(AbstractBaseUser):
)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
- age = models.PositiveIntegerField()
+ date_of_birth = models.DateField()
gender = models.CharField(max_length=200)
address = models.CharField(max_length=600)
preferred_lang = models.CharField(max_length=200)
@@ -108,7 +116,7 @@ class User(AbstractBaseUser):
REQUIRED_FIELDS = [
"first_name",
"last_name",
- "age",
+ "date_of_birth",
"gender",
"address",
"preferred_lang",
@@ -136,3 +144,30 @@ def is_staff(self):
"""Is the user a member of staff?"""
# Simplest possible answer: All admins are staff
return self.is_admin
+
+
+class Module(models.Model):
+ name = models.CharField(max_length=50)
+
+
+class Permission(models.Model):
+ name = models.CharField(max_length=50)
+ module = models.ForeignKey(Module, on_delete=models.CASCADE)
+
+
+class Role(models.Model):
+ name = models.CharField(max_length=50)
+ permissions = models.ManyToManyField(Permission)
+
+
+class UserRole(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ role = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True)
+
+
+class UserPreferences(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ dark_mode = models.BooleanField(default=False)
+
+ def __str__(self):
+ return f'{self.user.first_name} {self.user.last_name} Preferences'
diff --git a/backend/auth/accounts/serializers.py b/backend/auth/accounts/serializers.py
index 4e6515a..1768b12 100644
--- a/backend/auth/accounts/serializers.py
+++ b/backend/auth/accounts/serializers.py
@@ -1,5 +1,7 @@
from djoser.serializers import UserCreateSerializer
from django.contrib.auth import get_user_model
+from rest_framework import serializers
+from accounts.models import UserPreferences
User = get_user_model()
@@ -7,10 +9,11 @@
class UserCreateSerializer(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
- fields = ('email',
+ fields = (
+ 'email',
'first_name',
'last_name',
- 'age',
+ 'date_of_birth',
'gender',
'address',
'preferred_lang',
@@ -20,3 +23,9 @@ class Meta(UserCreateSerializer.Meta):
'experience',
'password',
)
+
+
+class UserPreferencesSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = UserPreferences
+ fields = ['dark_mode']
diff --git a/backend/auth/accounts/signals.py b/backend/auth/accounts/signals.py
new file mode 100644
index 0000000..a1e324d
--- /dev/null
+++ b/backend/auth/accounts/signals.py
@@ -0,0 +1,10 @@
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from .models import User, Role, UserRole
+
+
+@receiver(post_save, sender=User)
+def assign_default_role(sender, instance, created, **kwargs):
+ if created:
+ user_role = Role.objects.get(name='User')
+ UserRole.objects.create(user=instance, role=user_role)
diff --git a/backend/auth/accounts/templates/accounts/ActivationEmail.html b/backend/auth/accounts/templates/accounts/ActivationEmail.html
new file mode 100644
index 0000000..5024801
--- /dev/null
+++ b/backend/auth/accounts/templates/accounts/ActivationEmail.html
@@ -0,0 +1,28 @@
+{% load i18n %}
+
+{% block subject %}
+{% blocktrans %}Account activation on {{ site_name }}{% endblocktrans %}
+{% endblock subject %}
+
+{% block text_body %}
+{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}
+
+{% trans "Please go to the following page to activate account:" %}
+{{ protocol }}://{{ domain }}/{{ url|safe }}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+{% endblock text_body %}
+
+{% block html_body %}
+
{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}
+
+{% trans "Please go to the following page to activate account:" %}
+{{ protocol }}://{{ domain }}/{{ url|safe }}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+
+{% endblock html_body %}
\ No newline at end of file
diff --git a/backend/auth/accounts/templates/accounts/ConfirmationEmail.html b/backend/auth/accounts/templates/accounts/ConfirmationEmail.html
new file mode 100644
index 0000000..c8fd066
--- /dev/null
+++ b/backend/auth/accounts/templates/accounts/ConfirmationEmail.html
@@ -0,0 +1,21 @@
+{% load i18n %}
+
+{% block subject %}
+{% blocktrans %}{{ site_name }} - Your account has been successfully created and activated!{% endblocktrans %}
+{% endblock %}
+
+{% block text_body %}
+{% trans "Your account has been created and is ready to use!" %}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+{% endblock text_body %}
+
+{% block html_body %}
+{% trans "Your account has been created and is ready to use!" %}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+{% endblock html_body %}
\ No newline at end of file
diff --git a/backend/auth/accounts/tests.py b/backend/auth/accounts/tests.py
index 7ce503c..8f6f39c 100644
--- a/backend/auth/accounts/tests.py
+++ b/backend/auth/accounts/tests.py
@@ -1,3 +1,3 @@
from django.test import TestCase
-# Create your tests here.
+# Create your test here.
diff --git a/backend/auth/accounts/urls.py b/backend/auth/accounts/urls.py
new file mode 100644
index 0000000..8b50b48
--- /dev/null
+++ b/backend/auth/accounts/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path, include
+from rest_framework.routers import DefaultRouter
+from .views import UserPreferencesViewSet
+
+router = DefaultRouter()
+router.register(r'preferences', UserPreferencesViewSet, basename='userpreferences')
+
+urlpatterns = [
+ path('', include(router.urls)),
+]
diff --git a/backend/auth/accounts/views.py b/backend/auth/accounts/views.py
index 91ea44a..a44c2f1 100644
--- a/backend/auth/accounts/views.py
+++ b/backend/auth/accounts/views.py
@@ -1,3 +1,16 @@
from django.shortcuts import render
+from rest_framework import viewsets
+from .models import UserPreferences
+from .serializers import UserPreferencesSerializer
+from rest_framework.permissions import IsAuthenticated
-# Create your views here.
+
+class UserPreferencesViewSet(viewsets.ModelViewSet):
+ serializer_class = UserPreferencesSerializer
+ permission_classes = [IsAuthenticated]
+
+ def get_queryset(self):
+ return UserPreferences.objects.filter(user=self.request.user)
+
+ def perform_create(self, serializer):
+ serializer.save(user=self.request.user)
diff --git a/backend/auth/auth/settings.py b/backend/auth/auth/settings.py
index 55e7a51..4734039 100644
--- a/backend/auth/auth/settings.py
+++ b/backend/auth/auth/settings.py
@@ -12,7 +12,7 @@
import os.path
from datetime import timedelta
from pathlib import Path
-from .secrets import get_secret
+from .secrets import get_secrets
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -21,7 +21,7 @@
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
-get_secret()
+get_secrets()
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-!te*yedg2t@=j72f3xp!nxmu#ns52ubfr+z49q9*d-wdc_a1ow'
@@ -41,8 +41,9 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
+ 'corsheaders',
'djoser',
- 'accounts',
+ 'accounts'
]
MIDDLEWARE = [
@@ -53,6 +54,36 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'corsheaders.middleware.CorsMiddleware',
+]
+
+CORS_ORIGIN_ALLOW_ALL = True
+
+CORS_ALLOWED_ORIGINS = [
+ "http://localhost:3000",
+ "http://127.0.0.1:3000",
+]
+
+CORS_ALLOW_CREDENTIALS = True
+
+CORS_ALLOW_METHODS = [
+ "DELETE",
+ "GET",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+]
+CORS_ALLOW_HEADERS = [
+ "accept",
+ "accept-encoding",
+ "authorization",
+ "content-type",
+ "dnt",
+ "origin",
+ "user-agent",
+ "x-csrftoken",
+ "x-requested-with",
]
ROOT_URLCONF = 'auth.urls'
@@ -79,22 +110,33 @@
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
+# DATABASES = {
+# "default": {
+# "ENGINE": "django.db.backends.postgresql",
+# "NAME": os.environ.get('dbname', ''),
+# "USER": os.environ.get('username', ''),
+# "PASSWORD": os.environ.get('password', ''),
+# "HOST": os.environ.get('host', ''),
+# "PORT": os.environ.get('port', ''),
+# }
+# }
+
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
- "NAME": os.environ.get('dbname', ''),
- "USER": os.environ.get('username', ''),
- "PASSWORD": os.environ.get('password', ''),
- "HOST": os.environ.get('host', ''),
- "PORT": os.environ.get('port', ''),
+ "NAME": 'local-rds-schwarz',
+ "USER": 'postgres',
+ "PASSWORD": 'root',
+ "HOST": 'localhost',
+ "PORT": '5432',
}
}
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
-EMAIL_HOST_USER = 'schwarzstudios@gmail.com'
-EMAIL_HOST_PASSWORD = 'xlsyomssafccnnxa'
+EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
+EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = True
# Password validation
@@ -173,6 +215,7 @@
'USERNAME_RESET_CONFIRM_URL': 'email/reset/confirm/{uid}/{token}',
'ACTIVATION_URL': 'activate/{uid}/{token}',
'SEND_ACTIVATION_EMAIL': True,
+ 'SEND_ACTIVATION_EMAIL_TIMEOUT': 3600,
'SOCIAL_AUTH_TOKEN_STRATEGY': 'djoser.social.token.jwt.TokenStrategy',
'SOCIAL_AUTH_ALLOWED_REDIRECT_URIS': ['http://localhost:8000/google', 'http://localhost:8000/facebook'],
'SERIALIZERS': {
@@ -180,13 +223,17 @@
'user': 'accounts.serializers.UserCreateSerializer',
'current_user': 'accounts.serializers.UserCreateSerializer',
'user_delete': 'djoser.serializers.UserDeleteSerializer',
- }
+ },
+ 'EMAIL': {
+ 'activation': 'accounts.email.ActivationEmail',
+ 'confirmation': 'accounts.email.ConfirmationEmail',
+ },
}
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '[YOUR GOOGLE OAUTH2 API KEY]'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '[YOUR GOOGLE OAUTH2 API SECRET]'
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth'
- '/userinfo.profile', 'openid']
+ '/userinfo.Profile', 'openid']
SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_DATA = ['first_name', 'last_name']
SOCIAL_AUTH_FACEBOOK_KEY = '[YOUR FACEBOOK API KEY]'
@@ -202,3 +249,14 @@
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.User'
+
+# AWS S3 settings
+AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
+AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
+AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
+AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
+AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
+AWS_LOCATION = 'media'
+
+DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
+MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
diff --git a/backend/auth/auth/urls.py b/backend/auth/auth/urls.py
index 5329e70..93126f7 100644
--- a/backend/auth/auth/urls.py
+++ b/backend/auth/auth/urls.py
@@ -5,6 +5,7 @@
path('api/', include('djoser.urls')),
path('api/', include('djoser.urls.jwt')),
path('api/', include('djoser.social.urls')),
+ path('api/', include('accounts.urls')),
]
urlpatterns += [re_path(r'^.*', TemplateView.as_view(template_name='index.html'))]
diff --git a/backend/oauth/oauth/settings.py b/backend/oauth/oauth/settings.py
deleted file mode 100644
index ee7a92d..0000000
--- a/backend/oauth/oauth/settings.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""
-Django settings for oauth project.
-
-Generated by 'django-admin startproject' using Django 4.1.10.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/4.1/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/4.1/ref/settings/
-"""
-
-from pathlib import Path
-
-# Build paths inside the project like this: BASE_DIR / 'subdir'.
-BASE_DIR = Path(__file__).resolve().parent.parent
-
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'django-insecure-b12d0vdug42_bnii#pop$)dbe4kv&g%1k5m@csr&m#x11)0igw'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'allauth',
- 'allauth.account',
- 'allauth.socialaccount',
- 'allauth.socialaccount.providers.google',
- 'rest_framework',
-]
-
-SOCIALACCOUNT_PROVIDERS = {
- 'google': {
- 'SCOPE': [
- 'profile',
- 'email',
- ],
- 'AUTH_PARAMS': {
- 'access_type': 'online',
- }
- },
-}
-
-MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'allauth.account.middleware.AccountMiddleware',
-]
-
-ROOT_URLCONF = 'oauth.urls'
-
-TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
-]
-
-WSGI_APPLICATION = 'oauth.wsgi.application'
-
-
-# Database
-# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': BASE_DIR / 'db.sqlite3',
- }
-}
-
-
-# Password validation
-# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
-]
-
-
-# Internationalization
-# https://docs.djangoproject.com/en/4.1/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/4.1/howto/static-files/
-
-STATIC_URL = 'static/'
-
-# Default primary key field type
-# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
-
-DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
diff --git a/backend/oauth/oauth/urls.py b/backend/oauth/oauth/urls.py
deleted file mode 100644
index 7b1f304..0000000
--- a/backend/oauth/oauth/urls.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# urls.py in the oauth_service project
-
-from django.urls import path
-from allauth.socialaccount import views as socialaccount_views
-
-urlpatterns = [
- # Include OAuth URLs
- path('accounts/', socialaccount_views.connections, name='socialaccount_connections'),
- path('login/', socialaccount_views.login, name='account_login'),
- path('logout/', socialaccount_views.logout, name='account_logout'),
- path('signup/', socialaccount_views.signup, name='account_signup'),
- path('disconnect//', socialaccount_views.disconnect, name='socialaccount_disconnect'),
-]
diff --git a/backend/oauth/requirements.txt b/backend/oauth/requirements.txt
deleted file mode 100644
index a5378b7..0000000
Binary files a/backend/oauth/requirements.txt and /dev/null differ
diff --git a/backend/oauth/manage.py b/backend/user_profiles/manage.py
similarity index 84%
rename from backend/oauth/manage.py
rename to backend/user_profiles/manage.py
index c20c5b6..cca61ec 100644
--- a/backend/oauth/manage.py
+++ b/backend/user_profiles/manage.py
@@ -6,7 +6,7 @@
def main():
"""Run administrative tasks."""
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oauth.settings')
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_profiles.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@@ -18,5 +18,5 @@ def main():
execute_from_command_line(sys.argv)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/frontend/src/App.css b/backend/user_profiles/user_profiles/__init__.py
similarity index 100%
rename from frontend/src/App.css
rename to backend/user_profiles/user_profiles/__init__.py
diff --git a/backend/oauth/oauth/asgi.py b/backend/user_profiles/user_profiles/asgi.py
similarity index 71%
rename from backend/oauth/oauth/asgi.py
rename to backend/user_profiles/user_profiles/asgi.py
index 0aa0925..a597619 100644
--- a/backend/oauth/oauth/asgi.py
+++ b/backend/user_profiles/user_profiles/asgi.py
@@ -1,5 +1,5 @@
"""
-ASGI config for oauth project.
+ASGI config for user_profiles project.
It exposes the ASGI callable as a module-level variable named ``application``.
@@ -11,6 +11,6 @@
from django.core.asgi import get_asgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oauth.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_profiles.settings")
application = get_asgi_application()
diff --git a/backend/user_profiles/user_profiles/settings.py b/backend/user_profiles/user_profiles/settings.py
new file mode 100644
index 0000000..cd9a45f
--- /dev/null
+++ b/backend/user_profiles/user_profiles/settings.py
@@ -0,0 +1,126 @@
+"""
+Django settings for user_profiles project.
+
+Generated by 'django-admin startproject' using Django 4.1.10.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.1/ref/settings/
+"""
+
+from pathlib import Path
+
+# Other Django settings...
+
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "django-insecure-+_t0nutf0*l0$9lc7_$!8cra)+v7!+7^y&9vl31rics9o_j&_c"
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+]
+
+MIDDLEWARE = [
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+]
+
+ROOT_URLCONF = "user_profiles.urls"
+
+TEMPLATES = [
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = "user_profiles.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
+
+DATABASES = {
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": BASE_DIR / "db.sqlite3",
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+ },
+ {
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+ },
+ {
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
+ },
+ {
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.1/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.1/howto/static-files/
+
+STATIC_URL = "static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
diff --git a/backend/user_profiles/user_profiles/urls.py b/backend/user_profiles/user_profiles/urls.py
new file mode 100644
index 0000000..31876cf
--- /dev/null
+++ b/backend/user_profiles/user_profiles/urls.py
@@ -0,0 +1,21 @@
+"""user_profiles URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/4.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+
+urlpatterns = [
+ path("admin/", admin.site.urls),
+]
diff --git a/backend/oauth/oauth/wsgi.py b/backend/user_profiles/user_profiles/wsgi.py
similarity index 71%
rename from backend/oauth/oauth/wsgi.py
rename to backend/user_profiles/user_profiles/wsgi.py
index 76a479a..3d08c9a 100644
--- a/backend/oauth/oauth/wsgi.py
+++ b/backend/user_profiles/user_profiles/wsgi.py
@@ -1,5 +1,5 @@
"""
-WSGI config for oauth project.
+WSGI config for user_profiles project.
It exposes the WSGI callable as a module-level variable named ``application``.
@@ -11,6 +11,6 @@
from django.core.wsgi import get_wsgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oauth.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_profiles.settings")
application = get_wsgi_application()
diff --git a/frontend/index.html b/frontend/index.html
index 837a4f2..cae75c4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -27,7 +27,7 @@
- Schwarz | The all-in-one community for 3D Artists
+ Schwarz | The all-in-one platform for 3D Artists
You need to enable JavaScript to run this app.
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e6748b0..15080d8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -20,6 +20,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
+ "react-loading-skeleton": "^3.4.0",
"react-redux": "^9.0.4",
"react-router-dom": "^6.15.0",
"react-select": "^5.8.0",
@@ -32,10 +33,12 @@
},
"devDependencies": {
"@types/node": "^20.10.5",
+ "@vitest/ui": "^1.4.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
- "typescript": "^5.3.3"
+ "typescript": "^5.3.3",
+ "vitest": "^1.4.0"
}
},
"node_modules/@adobe/css-tools": {
@@ -658,6 +661,182 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
+ "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
+ "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
+ "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
+ "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
+ "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
+ "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
+ "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
+ "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
+ "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
+ "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
+ "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@esbuild/linux-loong64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
@@ -667,7 +846,183 @@
],
"optional": true,
"os": [
- "linux"
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
+ "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
+ "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
+ "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
+ "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
+ "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
+ "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
+ "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
+ "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
+ "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
],
"engines": {
"node": ">=12"
@@ -853,6 +1208,12 @@
"node": ">=14"
}
},
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.25",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
+ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==",
+ "dev": true
+ },
"node_modules/@redux-devtools/extension": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz",
@@ -896,6 +1257,201 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz",
+ "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz",
+ "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
+ "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz",
+ "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz",
+ "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
+ "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz",
+ "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz",
+ "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==",
+ "cpu": [
+ "ppc64le"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz",
+ "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz",
+ "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
+ "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz",
+ "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz",
+ "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz",
+ "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz",
+ "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -1168,6 +1724,12 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.2.tgz",
"integrity": "sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ=="
},
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
@@ -1286,66 +1848,241 @@
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
"integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==",
"dependencies": {
- "@types/jest": "*"
+ "@types/jest": "*"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.25",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.25.tgz",
+ "integrity": "sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
+ "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ=="
+ },
+ "node_modules/@vitejs/plugin-react-refresh": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-refresh/-/plugin-react-refresh-1.3.6.tgz",
+ "integrity": "sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==",
+ "deprecated": "This package has been deprecated in favor of @vitejs/plugin-react",
+ "dependencies": {
+ "@babel/core": "^7.14.8",
+ "@babel/plugin-transform-react-jsx-self": "^7.14.5",
+ "@babel/plugin-transform-react-jsx-source": "^7.14.5",
+ "@rollup/pluginutils": "^4.1.1",
+ "react-refresh": "^0.10.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react-refresh/node_modules/@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react-refresh/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/@vitejs/plugin-react-refresh/node_modules/react-refresh": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
+ "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz",
+ "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "1.4.0",
+ "@vitest/utils": "1.4.0",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz",
+ "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "1.4.0",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz",
+ "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==",
+ "dev": true,
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz",
+ "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==",
+ "dev": true,
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@types/use-sync-external-store": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
- "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
- },
- "node_modules/@types/yargs": {
- "version": "17.0.25",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.25.tgz",
- "integrity": "sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==",
+ "node_modules/@vitest/ui": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.4.0.tgz",
+ "integrity": "sha512-XC6CMhN1gzYcGbpn6/Oanj4Au2EXwQEX6vpcOeLlZv8dy7g11Ukx8zwtYQbwxs9duK2s9j2o5rbQiCP5DPAcmw==",
+ "dev": true,
"dependencies": {
- "@types/yargs-parser": "*"
+ "@vitest/utils": "1.4.0",
+ "fast-glob": "^3.3.2",
+ "fflate": "^0.8.1",
+ "flatted": "^3.2.9",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "sirv": "^2.0.4"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "1.4.0"
}
},
- "node_modules/@types/yargs-parser": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
- "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ=="
- },
- "node_modules/@vitejs/plugin-react-refresh": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-refresh/-/plugin-react-refresh-1.3.6.tgz",
- "integrity": "sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==",
- "deprecated": "This package has been deprecated in favor of @vitejs/plugin-react",
+ "node_modules/@vitest/utils": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz",
+ "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==",
+ "dev": true,
"dependencies": {
- "@babel/core": "^7.14.8",
- "@babel/plugin-transform-react-jsx-self": "^7.14.5",
- "@babel/plugin-transform-react-jsx-source": "^7.14.5",
- "@rollup/pluginutils": "^4.1.1",
- "react-refresh": "^0.10.0"
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
},
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
"engines": {
- "node": ">=12.0.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@vitejs/plugin-react-refresh/node_modules/@rollup/pluginutils": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
- "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "node_modules/@vitest/utils/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
"dependencies": {
- "estree-walker": "^2.0.1",
- "picomatch": "^2.2.2"
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
},
"engines": {
- "node": ">= 8.0.0"
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/@vitejs/plugin-react-refresh/node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ "node_modules/@vitest/utils/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
},
- "node_modules/@vitejs/plugin-react-refresh/node_modules/react-refresh": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
- "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
@@ -1415,6 +2152,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1558,6 +2304,15 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -1599,9 +2354,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001540",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001540.tgz",
- "integrity": "sha512-9JL38jscuTJBTcuETxm8QLsFr/F6v0CYYTEU6r5+qSM98P2Q0Hmu0eG1dTG5GBUmywU3UlcVOUSIJYY47rdFSw==",
+ "version": "1.0.30001632",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001632.tgz",
+ "integrity": "sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==",
"funding": [
{
"type": "opencollective",
@@ -1617,6 +2372,24 @@
}
]
},
+ "node_modules/chai": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
+ "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
@@ -1629,6 +2402,18 @@
"node": ">=8"
}
},
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -1795,6 +2580,18 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-equal": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz",
@@ -2300,6 +3097,38 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
"node_modules/expect": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
@@ -2352,6 +3181,12 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2368,6 +3203,12 @@
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
"node_modules/follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
@@ -2494,6 +3335,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@@ -2508,6 +3358,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
@@ -2653,6 +3515,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
"node_modules/immer": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz",
@@ -2912,6 +3783,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@@ -3262,6 +4145,12 @@
"node": ">=6"
}
},
+ "node_modules/jsonc-parser": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
+ "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
+ "dev": true
+ },
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -3276,6 +4165,22 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
+ "node_modules/local-pkg": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
+ "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+ "dev": true,
+ "dependencies": {
+ "mlly": "^1.4.2",
+ "pkg-types": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3292,6 +4197,15 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3308,11 +4222,29 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.8",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
+ "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3353,6 +4285,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -3385,6 +4329,27 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mlly": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
+ "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.3",
+ "pathe": "^1.1.2",
+ "pkg-types": "^1.0.3",
+ "ufo": "^1.3.2"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
+ "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -3441,6 +4406,33 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3506,6 +4498,36 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3581,6 +4603,21 @@
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -3615,10 +4652,21 @@
"node": ">= 6"
}
},
+ "node_modules/pkg-types": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
+ "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+ "dev": true,
+ "dependencies": {
+ "jsonc-parser": "^3.2.0",
+ "mlly": "^1.2.0",
+ "pathe": "^1.1.0"
+ }
+ },
"node_modules/postcss": {
- "version": "8.4.32",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
- "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -3636,7 +4684,7 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -3869,6 +4917,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "node_modules/react-loading-skeleton": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz",
+ "integrity": "sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-redux": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
@@ -4071,6 +5127,40 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rollup": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz",
+ "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.13.2",
+ "@rollup/rollup-android-arm64": "4.13.2",
+ "@rollup/rollup-darwin-arm64": "4.13.2",
+ "@rollup/rollup-darwin-x64": "4.13.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.13.2",
+ "@rollup/rollup-linux-arm64-musl": "4.13.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.13.2",
+ "@rollup/rollup-linux-x64-gnu": "4.13.2",
+ "@rollup/rollup-linux-x64-musl": "4.13.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.13.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.13.2",
+ "@rollup/rollup-win32-x64-msvc": "4.13.2",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -4149,6 +5239,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -4161,6 +5257,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sirv": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+ "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "dev": true,
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -4178,9 +5288,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
@@ -4204,6 +5314,18 @@
"node": ">=8"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
+ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
+ "dev": true
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -4305,6 +5427,18 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -4316,6 +5450,24 @@
"node": ">=8"
}
},
+ "node_modules/strip-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz",
+ "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==",
+ "dev": true,
+ "dependencies": {
+ "js-tokens": "^9.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz",
+ "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
+ "dev": true
+ },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -4428,6 +5580,30 @@
"node": ">=0.8"
}
},
+ "node_modules/tinybench": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
+ "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==",
+ "dev": true
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz",
+ "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -4447,6 +5623,15 @@
"node": ">=8.0"
}
},
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -4458,6 +5643,15 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/typescript": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
@@ -4471,6 +5665,12 @@
"node": ">=14.17"
}
},
+ "node_modules/ufo": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
+ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
+ "dev": true
+ },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -4568,6 +5768,137 @@
}
}
},
+ "node_modules/vite-node": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz",
+ "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==",
+ "dev": true,
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-loong64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+ "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/esbuild": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
+ "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.20.2",
+ "@esbuild/android-arm": "0.20.2",
+ "@esbuild/android-arm64": "0.20.2",
+ "@esbuild/android-x64": "0.20.2",
+ "@esbuild/darwin-arm64": "0.20.2",
+ "@esbuild/darwin-x64": "0.20.2",
+ "@esbuild/freebsd-arm64": "0.20.2",
+ "@esbuild/freebsd-x64": "0.20.2",
+ "@esbuild/linux-arm": "0.20.2",
+ "@esbuild/linux-arm64": "0.20.2",
+ "@esbuild/linux-ia32": "0.20.2",
+ "@esbuild/linux-loong64": "0.20.2",
+ "@esbuild/linux-mips64el": "0.20.2",
+ "@esbuild/linux-ppc64": "0.20.2",
+ "@esbuild/linux-riscv64": "0.20.2",
+ "@esbuild/linux-s390x": "0.20.2",
+ "@esbuild/linux-x64": "0.20.2",
+ "@esbuild/netbsd-x64": "0.20.2",
+ "@esbuild/openbsd-x64": "0.20.2",
+ "@esbuild/sunos-x64": "0.20.2",
+ "@esbuild/win32-arm64": "0.20.2",
+ "@esbuild/win32-ia32": "0.20.2",
+ "@esbuild/win32-x64": "0.20.2"
+ }
+ },
+ "node_modules/vite-node/node_modules/vite": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz",
+ "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.20.1",
+ "postcss": "^8.4.38",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vite-plugin-svgr": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-0.3.0.tgz",
@@ -4593,6 +5924,180 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/vitest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
+ "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "1.4.0",
+ "@vitest/runner": "1.4.0",
+ "@vitest/snapshot": "1.4.0",
+ "@vitest/spy": "1.4.0",
+ "@vitest/utils": "1.4.0",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.2",
+ "vite": "^5.0.0",
+ "vite-node": "1.4.0",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.4.0",
+ "@vitest/ui": "1.4.0",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-loong64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+ "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/esbuild": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
+ "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.20.2",
+ "@esbuild/android-arm": "0.20.2",
+ "@esbuild/android-arm64": "0.20.2",
+ "@esbuild/android-x64": "0.20.2",
+ "@esbuild/darwin-arm64": "0.20.2",
+ "@esbuild/darwin-x64": "0.20.2",
+ "@esbuild/freebsd-arm64": "0.20.2",
+ "@esbuild/freebsd-x64": "0.20.2",
+ "@esbuild/linux-arm": "0.20.2",
+ "@esbuild/linux-arm64": "0.20.2",
+ "@esbuild/linux-ia32": "0.20.2",
+ "@esbuild/linux-loong64": "0.20.2",
+ "@esbuild/linux-mips64el": "0.20.2",
+ "@esbuild/linux-ppc64": "0.20.2",
+ "@esbuild/linux-riscv64": "0.20.2",
+ "@esbuild/linux-s390x": "0.20.2",
+ "@esbuild/linux-x64": "0.20.2",
+ "@esbuild/netbsd-x64": "0.20.2",
+ "@esbuild/openbsd-x64": "0.20.2",
+ "@esbuild/sunos-x64": "0.20.2",
+ "@esbuild/win32-arm64": "0.20.2",
+ "@esbuild/win32-ia32": "0.20.2",
+ "@esbuild/win32-x64": "0.20.2"
+ }
+ },
+ "node_modules/vitest/node_modules/vite": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz",
+ "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.20.1",
+ "postcss": "^8.4.38",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
"node_modules/web-vitals": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
@@ -4660,6 +6165,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+ "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -4751,6 +6272,18 @@
"engines": {
"node": ">= 6"
}
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index f42f08c..7268397 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,6 +15,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
+ "react-loading-skeleton": "^3.4.0",
"react-redux": "^9.0.4",
"react-router-dom": "^6.15.0",
"react-select": "^5.8.0",
@@ -27,9 +28,9 @@
},
"scripts": {
"dev": "vite",
- "build": "tsc && vite build",
+ "build": "rd /s /q ..\\backend\\build && tsc && vite build && xcopy build ..\\backend\\build /e /i /y",
"serve": "vite preview",
- "test": "react-scripts test",
+ "test:ui": "vitest --ui --api 1337",
"eject": "react-scripts eject"
},
"eslintConfig": {
@@ -52,9 +53,11 @@
},
"devDependencies": {
"@types/node": "^20.10.5",
+ "@vitest/ui": "^1.4.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
- "typescript": "^5.3.3"
+ "typescript": "^5.3.3",
+ "vitest": "^1.4.0"
}
}
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
index 080d6c7..e485a6d 100644
--- a/frontend/public/manifest.json
+++ b/frontend/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "React App",
- "name": "Create React App Sample",
+ "short_name": "Schwarz",
+ "name": "Schwarz - The all-in-one platform for 3D Artists",
"icons": [
{
"src": "favicon.ico",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index d7340ce..4e4f312 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,51 +1,59 @@
import React from 'react';
-import './App.css';
import Otpscreen from "./pages/TwoFa/Otpscreen";
import TwofaForm from "./pages/TwoFa/TwofaOptions";
-import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
-import LoginForm from "./pages/Login/Login";
-import SettingsPage from "./pages/profile/settings";
+import {BrowserRouter as Router, Route, Routes} from "react-router-dom";
+import LoginForm from "./pages/Login/LoginScreen";
import SignupScreen from "./pages/Signup/SignupScreen";
-import SignupTab3 from "./pages/Signup/components/SignupTab3";
-import styles from './MyStyle.css';
-import Profile from './pages/profile/Profile';
+import {LandingScreen} from "./pages/LandingScreen";
+import Store from "./pages/Marketplace/MarketLanding";
+import Activate from "./pages/EmailActivation/Activate";
+import Layout from "./hocs/Layout";
+import {Provider} from "react-redux";
+import {store} from "./app/store";
+import UserAccount from "./pages/UserAccount/UserAccount";
function App() {
- return (
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/* } />*/}
-
-
-
-{/*
*/}
-{/*
*/}
-{/*
*/}
-{/*
*/}
-{/*
*/}
-{/*
*/}
-{/*
Loading... */}
-{/* */}
-{/*
*/}
-
- );
+ return (
+
+
+
+
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ } />
+ {/* } />*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* } />*/}
+
+
+
+
+
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
Loading... */}
+ {/* */}
+ {/*
*/}
+
+ );
}
diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js
deleted file mode 100644
index 1f03afe..0000000
--- a/frontend/src/App.test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- render( );
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/frontend/src/Routes.jsx b/frontend/src/Routes.jsx
new file mode 100644
index 0000000..4827d99
--- /dev/null
+++ b/frontend/src/Routes.jsx
@@ -0,0 +1,61 @@
+import React from "react";
+import {BrowserRouter as Router, Route, Routes} from "react-router-dom";
+import LoginForm from "./pages/Login/LoginScreen";
+import SignupScreen from "./pages/Signup/SignupScreen";
+import Otpscreen from "./pages/TwoFa/Otpscreen";
+import TwofaForm from "./pages/TwoFa/TwofaOptions";
+
+// import Home from "pages/Home";
+// import NotFound from "pages/NotFound";
+// const MyProfileHistory = React.lazy(() => import("pages/MyProfileHistory"));
+// const MyProfileWallet = React.lazy(() => import("pages/MyProfileWallet"));
+// const MyProfileCollection = React.lazy(
+// () => import("pages/MyProfileCollection"),
+// );
+// const MyProfile = React.lazy(() => import("pages/MyProfilePreview"));
+// const Saved = React.lazy(() => import("pages/Saved"));
+// const ActiveBid = React.lazy(() => import("pages/ActiveBid"));
+// const OtherUserProfile = React.lazy(() => import("pages/OtherUserProfile"));
+// const MarketDetail = React.lazy(() => import("pages/MarketDetail"));
+const Market = React.lazy(() => import("./pages/Marketplace/MarketLanding"));
+// const Settings = React.lazy(() => import("pages/Settings"));
+// const Message = React.lazy(() => import("pages/Message"));
+// const Dashboard = React.lazy(() => import("pages/Dashboard"));
+
+const ProjectRoutes = () => {
+ return (
+ Loading...>}>
+
+
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ }/>
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ {/* }*/}
+ {/*/>*/}
+ {/* } />*/}
+ {/* } />*/}
+ {/* } />*/}
+ }/>
+ }/>
+ }/>
+ }/>
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* } />*/}
+
+
+
+ );
+};
+export default ProjectRoutes;
diff --git a/frontend/src/assets/css/button.css b/frontend/src/assets/css/button.css
new file mode 100644
index 0000000..c1aa6c9
--- /dev/null
+++ b/frontend/src/assets/css/button.css
@@ -0,0 +1,34 @@
+:root {
+ --btn-shadow: 1px 1px 25px 10px rgba(255, 255, 255, 0.5);
+ --shine-degree: 120deg;
+ --shine-color: rgba(255, 255, 255, 0.2);
+ --shine-effect: linear-gradient(var(--shine-degree), transparent, var(--shine-color), transparent);
+ --shine-transition: all 0.65s ease-in-out;
+}
+.btn {
+ position: relative;
+ overflow: hidden;
+ text-decoration: none;
+}
+.btn:not(:last-child) {
+ margin-bottom: 3rem;
+}
+.btn::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: var(--shine-effect);
+}
+.btn-hover::before {
+ left: -100%;
+ transition: var(--shine-transition);
+}
+.btn-hover:hover {
+ box-shadow: var(--btn-shadow);
+ background-color: #232323;
+}
+.btn-hover:hover::before {
+ left: 100%;
+}
\ No newline at end of file
diff --git a/frontend/src/assets/css/dialogbox.css b/frontend/src/assets/css/dialogbox.css
new file mode 100644
index 0000000..17182b5
--- /dev/null
+++ b/frontend/src/assets/css/dialogbox.css
@@ -0,0 +1,22 @@
+.speech-bubble {
+ z-index: 10;
+ filter: drop-shadow(0px 2px 5px rgba(50, 50, 105, 0.15)) drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.05));
+}
+
+.speech-bubble:after {
+ z-index: 9;
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 20px;
+ width: 0;
+ height: 0;
+ border-left: 0px solid transparent;
+ border-right: 16px solid transparent;
+ border-top: 14px solid transparent;
+ border-bottom: 0;
+ border-top-color: white;
+ margin-left: -8px;
+ margin-bottom: -11px;
+ filter: drop-shadow(0px 2px 5px rgba(50, 50, 105, 0.02)) drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.02));
+}
diff --git a/frontend/src/assets/css/megamenu.css b/frontend/src/assets/css/megamenu.css
new file mode 100644
index 0000000..56efbb0
--- /dev/null
+++ b/frontend/src/assets/css/megamenu.css
@@ -0,0 +1,8 @@
+.menu-items li:hover .mega-menu {
+ opacity: 1;
+ visibility: visible;
+}
+
+.menu-items li:hover ~ .mega-container {
+ box-shadow: none !important;
+}
\ No newline at end of file
diff --git a/frontend/src/assets/css/sidebar.css b/frontend/src/assets/css/sidebar.css
new file mode 100644
index 0000000..7732283
--- /dev/null
+++ b/frontend/src/assets/css/sidebar.css
@@ -0,0 +1,12 @@
+.sidebar.open {
+ right: 0;
+}
+
+.overlay {
+ visibility: hidden;
+}
+
+.overlay.open {
+ opacity: 1;
+ visibility: visible;
+}
\ No newline at end of file
diff --git a/frontend/src/assets/css/tabsbar.css b/frontend/src/assets/css/tabsbar.css
new file mode 100644
index 0000000..a40ae42
--- /dev/null
+++ b/frontend/src/assets/css/tabsbar.css
@@ -0,0 +1,7 @@
+.scrollbar-hide {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+.scrollbar-hide::-webkit-scrollbar {
+ display: none; /* Chrome, Safari, Opera */
+}
\ No newline at end of file
diff --git a/frontend/src/assets/img/heart.svg b/frontend/src/assets/img/heart.svg
new file mode 100644
index 0000000..85c0a55
--- /dev/null
+++ b/frontend/src/assets/img/heart.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/src/assets/svg/account.jsx b/frontend/src/assets/svg/account.jsx
new file mode 100644
index 0000000..2280ded
--- /dev/null
+++ b/frontend/src/assets/svg/account.jsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export default function Account({width, height, fillColor}) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/addon.jsx b/frontend/src/assets/svg/addon.jsx
new file mode 100644
index 0000000..e183d2f
--- /dev/null
+++ b/frontend/src/assets/svg/addon.jsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+export default function Addon({ width, height }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/check.jsx b/frontend/src/assets/svg/check.jsx
new file mode 100644
index 0000000..2efc337
--- /dev/null
+++ b/frontend/src/assets/svg/check.jsx
@@ -0,0 +1,13 @@
+import React from "react"
+
+export default function Check({ fillColor, height, width }) {
+ return (
+
+
+
+ );
+}
+
+Check.defaultProps = {
+ height: "24px",
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/cross.jsx b/frontend/src/assets/svg/cross.jsx
new file mode 100644
index 0000000..d3bbfa8
--- /dev/null
+++ b/frontend/src/assets/svg/cross.jsx
@@ -0,0 +1,10 @@
+import React from "react"
+
+export default function Cross({ fillColor , height, width, onClick}) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/downarrow.jsx b/frontend/src/assets/svg/downarrow.jsx
new file mode 100644
index 0000000..2f6f647
--- /dev/null
+++ b/frontend/src/assets/svg/downarrow.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export default function DownArrow({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/filter.jsx b/frontend/src/assets/svg/filter.jsx
new file mode 100644
index 0000000..8fe3bc8
--- /dev/null
+++ b/frontend/src/assets/svg/filter.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export default function Filter({width, height, fillColor}) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/hdri.jsx b/frontend/src/assets/svg/hdri.jsx
new file mode 100644
index 0000000..191c431
--- /dev/null
+++ b/frontend/src/assets/svg/hdri.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+export default function HDRI({ width, height }) {
+ return (
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/leftarrow.jsx b/frontend/src/assets/svg/leftarrow.jsx
new file mode 100644
index 0000000..84bf407
--- /dev/null
+++ b/frontend/src/assets/svg/leftarrow.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+
+export default function LeftArrow({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/menu.jsx b/frontend/src/assets/svg/menu.jsx
new file mode 100644
index 0000000..a8cce97
--- /dev/null
+++ b/frontend/src/assets/svg/menu.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+
+export default function Menu({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/model.jsx b/frontend/src/assets/svg/model.jsx
new file mode 100644
index 0000000..67de04a
--- /dev/null
+++ b/frontend/src/assets/svg/model.jsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+export default function Model({fillColor, width, height}) {
+ return (
+
+
+
+
+ {" "}
+
+ {" "}
+
+
+
+ {" "}
+
+ {" "}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/notifications.jsx b/frontend/src/assets/svg/notifications.jsx
new file mode 100644
index 0000000..952d250
--- /dev/null
+++ b/frontend/src/assets/svg/notifications.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+
+export default function Notifications({ fillColor, height, width, classname }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/openinnew.jsx b/frontend/src/assets/svg/openinnew.jsx
new file mode 100644
index 0000000..67fa43a
--- /dev/null
+++ b/frontend/src/assets/svg/openinnew.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+
+export default function OpenInNew({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/rig.jsx b/frontend/src/assets/svg/rig.jsx
new file mode 100644
index 0000000..8e04ab3
--- /dev/null
+++ b/frontend/src/assets/svg/rig.jsx
@@ -0,0 +1,17 @@
+import React from "react";
+
+export default function Rig({ width, height }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/rightarrow.jsx b/frontend/src/assets/svg/rightarrow.jsx
new file mode 100644
index 0000000..12e7846
--- /dev/null
+++ b/frontend/src/assets/svg/rightarrow.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export default function RightArrow({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/texture.jsx b/frontend/src/assets/svg/texture.jsx
new file mode 100644
index 0000000..0589c17
--- /dev/null
+++ b/frontend/src/assets/svg/texture.jsx
@@ -0,0 +1,189 @@
+import React from "react";
+
+export default function Texture({ width, height }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/assets/svg/uparrow.jsx b/frontend/src/assets/svg/uparrow.jsx
new file mode 100644
index 0000000..f030789
--- /dev/null
+++ b/frontend/src/assets/svg/uparrow.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export default function UpArrow({ width, height, fillColor }) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/SubmitButton.jsx b/frontend/src/components/Button.jsx
similarity index 65%
rename from frontend/src/components/SubmitButton.jsx
rename to frontend/src/components/Button.jsx
index d32a489..7dbd3e0 100644
--- a/frontend/src/components/SubmitButton.jsx
+++ b/frontend/src/components/Button.jsx
@@ -1,33 +1,31 @@
import React from "react";
+import '../assets/css/button.css'
-const SubmitButton = ({classname, text, onClick, isForwardStep, isPreviousStep}) => {
+export const Button = ({classname, text, onClick, isForwardStep, isPreviousStep, type, ...restProps}) => {
return (
- {isPreviousStep ? (
+ className={"btn btn-hover bg-black text-white flex items-center justify-center overflow-hidden rounded-xl " + classname}
+ onClick={onClick}
+ type={type}
+ {...restProps}>
+ {isPreviousStep &&
-
- ) : null
}
{text}
- {isForwardStep ? (
-
- ) : null
}
);
-};
-
-export default SubmitButton;
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/frontend/src/components/Button/index.jsx b/frontend/src/components/Button/index.jsx
deleted file mode 100644
index e98d9dd..0000000
--- a/frontend/src/components/Button/index.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-
-const shapes = { round: "rounded-[10px]", circle: "rounded-[50%]" };
-const variants = {
- outline: {
- white_A700: "outline outline-[0.75px] outline-white-A700 text-white-A700",
- gray_900: "outline outline-[0.5px] outline-gray-900 text-gray-900",
- gray_100: "outline outline-[0.75px] outline-gray-100 text-black-900",
- },
- fill: {
- gray_900: "bg-gray-900 text-white-A700",
- gray_100: "bg-gray-100 text-gray-500",
- green_600: "bg-green-600 text-white-A700",
- gray_900_67: "bg-gray-900_67",
- black_900: "bg-black-900 text-white-A700",
- white_A700: "bg-white-A700 text-gray-900",
- gray_50: "bg-gray-50 text-black-900",
- gray_900_26: "bg-gray-900_26 text-white-A700",
- },
-};
-const sizes = {
- xs: "p-[7px]",
- sm: "pr-[9px] py-[9px]",
- md: "p-2.5",
- lg: "p-[13px]",
- xl: "pr-[15px] py-[15px]",
- "2xl": "p-4",
-};
-
-const Button = ({
- children,
- className = "",
- leftIcon,
- rightIcon,
- shape = "",
- size = "",
- variant = "",
- color = "",
- ...restProps
-}) => {
- return (
-
- {!!leftIcon && leftIcon}
- {children}
- {!!rightIcon && rightIcon}
-
- );
-};
-
-Button.propTypes = {
- className: PropTypes.string,
- children: PropTypes.node,
- shape: PropTypes.oneOf(["round", "circle"]),
- size: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl", "2xl"]),
- variant: PropTypes.oneOf(["outline", "fill"]),
- color: PropTypes.oneOf([
- "white_A700",
- "gray_900",
- "gray_100",
- "green_600",
- "gray_900_67",
- "black_900",
- "gray_50",
- "gray_900_26",
- ]),
-};
-
-export { Button };
diff --git a/frontend/src/components/CodeField.jsx b/frontend/src/components/CodeField.jsx
new file mode 100644
index 0000000..311818e
--- /dev/null
+++ b/frontend/src/components/CodeField.jsx
@@ -0,0 +1,83 @@
+import React, { useState, useRef, useEffect } from "react";
+
+const CodeField = () => {
+ const [otp, setOtp] = useState(["", "", "", "", "", ""]);
+ const inputs = Array.from({ length: 6 }, (_, index) => useRef(null));
+
+ const handleInputChange = (index, event) => {
+ const value = event.target.value;
+
+ if (isNaN(value) || value === "") {
+ return; // Allow only numeric input
+ }
+
+ const newOtp = [...otp];
+ newOtp[index] = value;
+
+ setOtp(newOtp);
+
+ // Move to the next input
+ if (index < otp.length - 1 && value !== "") {
+ inputs[index + 1].current.focus();
+ }
+ };
+
+ const handleKeyDown = (index, event) => {
+ if (event.key === "Backspace" && index > 0 && otp[index] === "") {
+ // Move to the previous input on Backspace press
+ inputs[index - 1].current.focus();
+ } else if (event.key === "Backspace" && otp[index] !== "") {
+ // Clear the current input if it's not empty
+ const newOtp = [...otp];
+ newOtp[index] = "";
+ setOtp(newOtp);
+ }
+ };
+
+ useEffect(() => {
+ // Focus the first input on initial mount
+ inputs[0].current.focus();
+ }, []);
+
+ return (
+
+
+
+
+
+
OTP Verification
+
+ Enter the OTP you received at
+ +91 ******876
+
+
+
+ {otp.map((value, index) => (
+ handleInputChange(index, event)}
+ onKeyDown={(event) => handleKeyDown(index, event)}
+ />
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CodeField;
diff --git a/frontend/src/components/DialogBox.jsx b/frontend/src/components/DialogBox.jsx
new file mode 100644
index 0000000..84730c3
--- /dev/null
+++ b/frontend/src/components/DialogBox.jsx
@@ -0,0 +1,10 @@
+import React from "react"
+import "../assets/css/dialogbox.css"
+
+export const DialogBox = ({classname, content}) => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/ErrorMessage/index.jsx b/frontend/src/components/ErrorMessage/index.jsx
deleted file mode 100644
index ac1584e..0000000
--- a/frontend/src/components/ErrorMessage/index.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-
-export const ErrorMessage = ({ errors = [], className = "" }) => {
- return (
- errors?.length > 0 && (
-
- {errors.join(", ")}
-
- )
- );
-};
diff --git a/frontend/src/components/Heading.jsx b/frontend/src/components/Heading.jsx
new file mode 100644
index 0000000..9f05cb5
--- /dev/null
+++ b/frontend/src/components/Heading.jsx
@@ -0,0 +1,11 @@
+import React from "react";
+import {motion} from "framer-motion"
+
+export const Heading = ({text, initial, animate, transition, classname, ...restProps}) => {
+ return (
+
+ {text}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/HorizontalTabsBar.jsx b/frontend/src/components/HorizontalTabsBar.jsx
new file mode 100644
index 0000000..75b505b
--- /dev/null
+++ b/frontend/src/components/HorizontalTabsBar.jsx
@@ -0,0 +1,100 @@
+// import React from "react";
+// import {Tab} from "./Tab";
+//
+// export const HorizontalTabsBar = ({ classname, tabs, activeTab, setActiveTab }) => {
+// return (
+//
+// {tabs.map((tab, index) => (
+// setActiveTab(index)}
+// />
+// ))}
+//
+// );
+// }
+
+import React, { useRef, useEffect, useState } from "react";
+import { Tab } from "./Tab";
+import LeftArrow from "../assets/svg/leftarrow";
+import RightArrow from "../assets/svg/rightarrow";
+
+export const HorizontalTabsBar = ({ classname, tabs, activeTab, setActiveTab }) => {
+ const containerRef = useRef(null);
+ const [showLeftArrow, setShowLeftArrow] = useState(false);
+ const [showRightArrow, setShowRightArrow] = useState(false);
+
+ useEffect(() => {
+ const updateArrows = () => {
+ if (containerRef.current) {
+ const { scrollLeft, scrollWidth, clientWidth } = containerRef.current;
+ setShowLeftArrow(scrollLeft > 0);
+ setShowRightArrow(scrollLeft + clientWidth < scrollWidth);
+ }
+ };
+
+ updateArrows();
+
+ window.addEventListener("resize", updateArrows);
+ return () => window.removeEventListener("resize", updateArrows);
+ }, []);
+
+ const scrollLeft = () => {
+ containerRef.current.scrollBy({ left: -200, behavior: "smooth" });
+ };
+
+ const scrollRight = () => {
+ containerRef.current.scrollBy({ left: 200, behavior: "smooth" });
+ };
+
+ const updateTab = () => {
+ document.title = `${tabs[activeTab].text} | Schwarz`
+ }
+
+ return (
+
+ {showLeftArrow && (
+
+
+
+ )}
+
{
+ const { scrollLeft, scrollWidth, clientWidth } = containerRef.current;
+ setShowLeftArrow(scrollLeft > 0);
+ setShowRightArrow(scrollLeft + clientWidth < scrollWidth);
+ }}
+ >
+ {tabs.map((tab, index) => (
+ {
+ setActiveTab(index);
+ }}
+ />
+ ))}
+
+ {showRightArrow && (
+
+
+
+ )}
+
+ );
+};
diff --git a/frontend/src/components/Image.jsx b/frontend/src/components/Image.jsx
new file mode 100644
index 0000000..ba90b92
--- /dev/null
+++ b/frontend/src/components/Image.jsx
@@ -0,0 +1,19 @@
+import React from "react";
+
+const Img = ({
+ className,
+ src = "defaultNoData.png",
+ alt = "testImg",
+ ...restProps
+ }) => {
+ return (
+
+ );
+};
+export {Img};
diff --git a/frontend/src/components/Img/index.jsx b/frontend/src/components/Img/index.jsx
deleted file mode 100644
index 74a4db4..0000000
--- a/frontend/src/components/Img/index.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from "react";
-
-const Img = ({
- className,
- src = "defaultNoData.png",
- alt = "testImg",
- ...restProps
-}) => {
- return (
-
- );
-};
-export { Img };
diff --git a/frontend/src/components/Input/index.jsx b/frontend/src/components/Input/index.jsx
deleted file mode 100644
index cc4d4b7..0000000
--- a/frontend/src/components/Input/index.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-import { ErrorMessage } from "../../components/ErrorMessage";
-
-const variants = {
- outline: {
- gray_400: "border border-gray-400 border-solid text-black-900",
- gray_300: "border border-gray-300 border-solid",
- },
- fill: { gray_50: "bg-gray-50 text-gray-500", white_A700: "bg-white-A700" },
-};
-const shapes = { round: "rounded-[15px]" };
-const sizes = {
- xs: "pb-[11px] pr-[11px] pt-[13px]",
- md: "pb-[18px] pr-[18px] pt-[19px]",
- sm: "pb-[15px] pt-[18px]",
-};
-
-const Input = React.forwardRef(
- (
- {
- wrapClassName = "",
- className = "",
- name = "",
- placeholder = "",
- type = "text",
- children,
- errors = [],
- label = "",
- prefix,
- suffix,
- onChange,
- shape = "",
- size = "",
- variant = "fill",
- color = "white_A700",
- ...restProps
- },
- ref,
- ) => {
- const handleChange = (e) => {
- if (onChange) onChange(e?.target?.value);
- };
-
- return (
- <>
-
- {!!label && label}
- {!!prefix && prefix}
-
- {!!suffix && suffix}
-
- {!!errors && }
- >
- );
- },
-);
-
-Input.propTypes = {
- wrapClassName: PropTypes.string,
- className: PropTypes.string,
- name: PropTypes.string,
- placeholder: PropTypes.string,
- type: PropTypes.string,
- shape: PropTypes.oneOf(["round"]),
- size: PropTypes.oneOf(["xs", "md", "sm"]),
- variant: PropTypes.oneOf(["outline", "fill"]),
- color: PropTypes.oneOf(["gray_400", "gray_300", "gray_50", "white_A700"]),
-};
-
-export { Input };
diff --git a/frontend/src/components/InputField.jsx b/frontend/src/components/InputField.jsx
index 3242907..e6e506c 100644
--- a/frontend/src/components/InputField.jsx
+++ b/frontend/src/components/InputField.jsx
@@ -1,20 +1,31 @@
import React from "react";
+import {motion} from "framer-motion"
-const InputField = ({ label, type, placeholder, value, onChange, error, showError }) => {
- return (
-
-
{label}
-
- {error && showError &&
{error}
}
-
- );
+const InputField = ({label, type, placeholder, pattern, value = "", onChange, error, onFocus, onBlur, readonly, classname}) => {
+ return (
+
+ {label}
+
+ {error &&
+ {error} }
+
+ );
};
export default InputField;
\ No newline at end of file
diff --git a/frontend/src/components/Line/index.jsx b/frontend/src/components/Line/index.jsx
deleted file mode 100644
index 24fff00..0000000
--- a/frontend/src/components/Line/index.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import React from "react";
-
-const Line = ({ className, ...restProps }) => {
- return
;
-};
-export { Line };
diff --git a/frontend/src/components/List/index.jsx b/frontend/src/components/List/index.jsx
deleted file mode 100644
index 04de0d6..0000000
--- a/frontend/src/components/List/index.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from "react";
-
-const List = ({ children, className, ...restProps }) => {
- return (
-
- {children}
-
- );
-};
-export { List };
diff --git a/frontend/src/components/SearchBar.jsx b/frontend/src/components/SearchBar.jsx
index 6b97535..80c0572 100644
--- a/frontend/src/components/SearchBar.jsx
+++ b/frontend/src/components/SearchBar.jsx
@@ -1,7 +1,38 @@
-import React from 'react'
+import React, {useState} from 'react'
export default function SearchBar() {
- return (
- SearchBar
- )
+ const [isClicked, setIsClicked] = useState(false);
+ return (
+
+
+
+
{
+ setIsClicked(true)
+ }}
+ />
+
+
+ );
}
diff --git a/frontend/src/components/SelectBox/index.jsx b/frontend/src/components/SelectBox/index.jsx
deleted file mode 100644
index 069edbb..0000000
--- a/frontend/src/components/SelectBox/index.jsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import React from "react";
-import Select from "react-select";
-import PropTypes from "prop-types";
-import { ErrorMessage } from "../ErrorMessage";
-
-const SelectBox = React.forwardRef(
- (
- {
- children,
- placeholder = "Select",
- className = "",
- options = [],
- isSearchable = false,
- placeholderClassName = "",
- isMulti = false,
- onChange,
- value = "",
- errors = [],
- indicator,
- shape = "",
- size = "",
- variant = "",
- color = "",
- ...restProps
- },
- ref,
- ) => {
- const [selectedVal, setSelectedVal] = React.useState(value);
-
- const handleChange = (data) => {
- setSelectedVal(data);
- if (isMulti) {
- onChange?.(data?.map((d) => d.value) || []);
- } else {
- onChange?.(data?.value);
- }
- };
-
- return (
- <>
- {placeholder}
- }
- isSearchable={isSearchable}
- isMulti={isMulti}
- components={{
- IndicatorSeparator: () => null,
- ...(indicator && { DropdownIndicator: () => indicator }),
- }}
- value={selectedVal}
- onChange={handleChange}
- styles={{
- container: (provided) => ({
- ...provided,
- zIndex: 0,
- }),
- control: (provided) => ({
- ...provided,
- backgroundColor: "transparent",
- border: "0 !important",
- boxShadow: "0 !important",
- minHeight: "auto",
- "&:hover": {
- border: "0 !important",
- },
- }),
- option: (provided, state) => ({
- ...provided,
- color: state.isSelected && "#fafafa",
- backgroundColor: state.isSelected && "#130c1a26",
- "&:hover": { backgroundColor: "#130c1a26", color: "#ffffff" },
- }),
- singleValue: (provided) => ({
- ...provided,
- color: "inherit",
- }),
- input: (provided) => ({
- ...provided,
- color: "inherit",
- margin: "0",
- padding: "0",
- // height: "0",
- }),
- valueContainer: (provided) => ({
- ...provided,
- padding: "0",
- }),
- dropdownIndicator: (provided) => ({
- ...provided,
- paddingTop: "0px",
- paddingBottom: "0px",
- }),
- clearIndicator: (provided) => ({
- ...provided,
- padding: "0",
- }),
- multiValueLabel: (provided) => ({
- ...provided,
- padding: "0",
- }),
- menuPortal: (base) => ({ ...base, zIndex: 999999 }),
- placeholder: (base) => ({
- ...base,
- margin: 0,
- }),
- }}
- menuPortalTarget={document.body}
- closeMenuOnScroll={(event) => {
- return event.target.id === "scrollContainer";
- }}
- {...restProps}
- />
-
- {children}
- >
- );
- },
-);
-
-SelectBox.propTypes = {
- placeholder: PropTypes.string,
- className: PropTypes.string,
- options: PropTypes.array,
- isSearchable: PropTypes.bool,
- placeholderClassName: PropTypes.string,
- isMulti: PropTypes.bool,
- onChange: PropTypes.func,
- value: PropTypes.string,
-};
-
-SelectBox.defaultProps = {
- placeholder: "Select",
- className: "",
- isSearchable: false,
- placeholderClassName: "",
- isMulti: false,
- value: "",
- options: [],
- onChange: () => {},
-};
-export { SelectBox };
diff --git a/frontend/src/components/SelectField.jsx b/frontend/src/components/SelectField.jsx
index 413ddc3..6a53f29 100644
--- a/frontend/src/components/SelectField.jsx
+++ b/frontend/src/components/SelectField.jsx
@@ -1,6 +1,6 @@
import React from "react";
-const SelectField = ({label, value, onChange, error, showError, options, defaultOption}) => {
+const SelectField = ({label, value, onChange, error, options, defaultOption}) => {
return (
@@ -8,19 +8,19 @@ const SelectField = ({label, value, onChange, error, showError, options, default
{options.map((option) => (
-
+
{option}
))}
- {error && showError && (
+ {error && (
{error}
)}
diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx
new file mode 100644
index 0000000..ad72df7
--- /dev/null
+++ b/frontend/src/components/Sidebar.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import '../assets/css/sidebar.css';
+import {SubHeading} from "./SubHeading";
+import Cross from "../assets/svg/cross";
+
+const Sidebar = ({isOpen, closeSidebar}) => {
+ return (
+ <>
+
+
+
+ {/* Add your filter options here */}
+
+
+
+ >
+ );
+};
+
+export default Sidebar;
diff --git a/frontend/src/components/SubHeading.jsx b/frontend/src/components/SubHeading.jsx
new file mode 100644
index 0000000..9d6d337
--- /dev/null
+++ b/frontend/src/components/SubHeading.jsx
@@ -0,0 +1,15 @@
+import React from "react";
+import {motion} from "framer-motion"
+import {Link} from "react-router-dom";
+
+export const SubHeading = ({text, classname, initial, animate, transition, ...restProps}) => {
+ return (
+
+
+ {text}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/Tab.jsx b/frontend/src/components/Tab.jsx
new file mode 100644
index 0000000..4609e25
--- /dev/null
+++ b/frontend/src/components/Tab.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+export const Tab = ( {text, icon, isActive, onClick, classname, ...restProps} ) => {
+ return (
+
+ {icon}
+ {text}
+
+ );
+};
diff --git a/frontend/src/components/Text.jsx b/frontend/src/components/Text.jsx
new file mode 100644
index 0000000..5cc117c
--- /dev/null
+++ b/frontend/src/components/Text.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+const Text = ({children, className = "", size, as, ...restProps}) => {
+ const Component = as || "p";
+
+ return (
+
+ {children}
+
+ );
+};
+
+export {Text};
diff --git a/frontend/src/components/Text/index.jsx b/frontend/src/components/Text/index.jsx
deleted file mode 100644
index 884948f..0000000
--- a/frontend/src/components/Text/index.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from "react";
-
-const sizeClasses = {
- txtOutfitMedium14: "font-medium font-outfit",
- txtUrbanistRegular14WhiteA700ab: "font-normal font-urbanist",
- txtOutfitRegular14Red600: "font-normal font-outfit",
- txtOutfitMedium18: "font-medium font-outfit",
- txtUrbanistSemiBold32Black900: "font-semibold font-urbanist",
- txtUrbanistSemiBold24: "font-semibold font-urbanist",
- txtUrbanistSemiBold20: "font-semibold font-urbanist",
- txtUrbanistMedium18: "font-medium font-urbanist",
- txtUrbanistRegular14Red600: "font-normal font-urbanist",
- txtOutfitSemiBold24: "font-outfit font-semibold",
- txtUrbanistRegular16: "font-normal font-urbanist",
- txtUrbanistRegular14WhiteA700a2: "font-normal font-urbanist",
- txtUrbanistMedium12Gray500: "font-medium font-urbanist",
- txtUrbanistMedium18Black900: "font-medium font-urbanist",
- txtUrbanistRegular18: "font-normal font-urbanist",
- txtUrbanistMedium14: "font-medium font-urbanist",
- txtUrbanistSemiBold16: "font-semibold font-urbanist",
- txtUrbanistSemiBold18: "font-semibold font-urbanist",
- txtOutfitMedium14Red600: "font-medium font-outfit",
- txtUrbanistMedium16: "font-medium font-urbanist",
- txtUrbanistMedium16Gray500ab: "font-medium font-urbanist",
- txtUrbanistMedium14Gray500: "font-medium font-urbanist",
- txtUrbanistRegular12: "font-normal font-urbanist",
- txtUrbanistMedium14Gray900: "font-medium font-urbanist",
- txtUrbanistMedium12Gray901: "font-medium font-urbanist",
- txtUrbanistRegular14: "font-normal font-urbanist",
- txtOutfitRegular14: "font-normal font-outfit",
- txtOutfitRegular10: "font-normal font-outfit",
- txtUrbanistMedium14Black900: "font-medium font-urbanist",
- txtUrbanistSemiBold34: "font-semibold font-urbanist",
- txtUrbanistMedium12: "font-medium font-urbanist",
- txtUrbanistSemiBold14: "font-semibold font-urbanist",
- txtUrbanistMedium14Green600: "font-medium font-urbanist",
- txtUrbanistRegular14Green600: "font-normal font-urbanist",
- txtUrbanistSemiBold32: "font-semibold font-urbanist",
- txtUrbanistRegular12Black900: "font-normal font-urbanist",
- txtUrbanistRegular14Gray500: "font-normal font-urbanist",
- txtUrbanistMedium12WhiteA700: "font-medium font-urbanist",
- txtOutfitSemiBold18: "font-outfit font-semibold",
- txtUrbanistSemiBold18Black900: "font-semibold font-urbanist",
- txtUrbanistRegular14Gray900ab: "font-normal font-urbanist",
- txtUrbanistRegular14WhiteA700: "font-normal font-urbanist",
-};
-
-const Text = ({ children, className = "", size, as, ...restProps }) => {
- const Component = as || "p";
-
- return (
-
- {children}
-
- );
-};
-
-export { Text };
diff --git a/frontend/src/components/TextArea/index.jsx b/frontend/src/components/TextArea/index.jsx
deleted file mode 100644
index 30b8ab5..0000000
--- a/frontend/src/components/TextArea/index.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-import { ErrorMessage } from "../../components/ErrorMessage";
-
-const TextArea = React.forwardRef(
- (
- { className, name, placeholder, children, errors, onChange, ...restProps },
- ref,
- ) => {
- const handleChange = (e) => {
- if (onChange) onChange(e?.target?.value);
- };
-
- return (
- <>
-
- {!!errors && }
-
- {children}
- >
- );
- },
-);
-
-TextArea.propTypes = {
- className: PropTypes.string,
- name: PropTypes.string,
- placeholder: PropTypes.string,
-};
-
-TextArea.defaultProps = { className: "", name: "", placeholder: "" };
-export { TextArea };
diff --git a/frontend/src/components/UserProfileDropdown.jsx b/frontend/src/components/UserProfileDropdown.jsx
new file mode 100644
index 0000000..586dff0
--- /dev/null
+++ b/frontend/src/components/UserProfileDropdown.jsx
@@ -0,0 +1,118 @@
+import React, { useState, useContext, useEffect, useRef } from "react";
+import { Link } from "react-router-dom";
+import { PreferencesContext } from "../contexts/PreferencesContext";
+import { motion, AnimatePresence } from "framer-motion";
+import { generateGradient } from "../features/userProfile/profileGradient";
+import {logout} from "../services/actions/auth";
+import Skeleton from "react-loading-skeleton";
+
+export const UserProfileDropdown = ({ first_name, last_name, email }) => {
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const { darkMode, toggleDarkMode } = useContext(PreferencesContext);
+ const dropdownRef = useRef(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ setLoading(false);
+ }, []);
+
+ const profileGradient = generateGradient(email);
+
+ const toggleDropdown = () => {
+ setIsDropdownOpen((prev) => !prev);
+ };
+
+ const handleClickOutside = (event) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setIsDropdownOpen(false);
+ }
+ };
+
+ useEffect(() => {
+ if (isDropdownOpen) {
+ document.addEventListener("mousedown", handleClickOutside);
+ } else {
+ document.removeEventListener("mousedown", handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [isDropdownOpen]);
+
+ return (
+
+ {/* User icon */}
+ {loading ?
:
+
+ {first_name[0]}{last_name[0]}
+ }
+
+ {/* Dropdown menu */}
+
+ {isDropdownOpen && (
+
+
+
{first_name} {last_name}
+
{email}
+
+
+
+
+
+
+
+ My Account
+
+
+
+
+
+ My Cart
+
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/frontend/src/components/VerticalTab.jsx b/frontend/src/components/VerticalTab.jsx
new file mode 100644
index 0000000..d81ad00
--- /dev/null
+++ b/frontend/src/components/VerticalTab.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export const VerticalTab = ({text, icon, isActive, onClick, classname, ...restProps}) => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/VerticalTabsBar.jsx b/frontend/src/components/VerticalTabsBar.jsx
new file mode 100644
index 0000000..f754b9e
--- /dev/null
+++ b/frontend/src/components/VerticalTabsBar.jsx
@@ -0,0 +1,24 @@
+import React from "react";
+import {VerticalTab} from "./VerticalTab";
+
+export const VerticalTabsBar = ({classname, tabs, activeTab, setActiveTab}) => {
+ return (
+
+ {tabs.map((tab, tabIndex) => (
+
+
{tab.text}
+ {tab.subtabs.map((subtab, subtabIndex) => (
+
setActiveTab([tabIndex, subtabIndex])}
+ />
+ ))}
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js
deleted file mode 100644
index e31d401..0000000
--- a/frontend/src/components/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export { Button } from "./Button";
-export { Img } from "./Img";
-export { Input } from "./Input";
-export { Line } from "./Line";
-export { List } from "./List";
-export { SelectBox } from "./SelectBox";
-export { Text } from "./Text";
-export { TextArea } from "./TextArea";
diff --git a/frontend/src/contexts/PreferencesContext.jsx b/frontend/src/contexts/PreferencesContext.jsx
new file mode 100644
index 0000000..ae5f30b
--- /dev/null
+++ b/frontend/src/contexts/PreferencesContext.jsx
@@ -0,0 +1,28 @@
+import React, { createContext, useState, useEffect } from 'react';
+
+export const PreferencesContext = createContext();
+
+export const PreferencesProvider = ({ children }) => {
+ const [darkMode, setDarkMode] = useState(() => {
+ return localStorage.getItem('darkMode') === 'true';
+ });
+
+ useEffect(() => {
+ localStorage.setItem('darkMode', darkMode);
+ if (darkMode) {
+ document.documentElement.classList.add('dark');
+ } else {
+ document.documentElement.classList.remove('dark');
+ }
+ }, [darkMode]);
+
+ const toggleDarkMode = () => {
+ setDarkMode(prevMode => !prevMode);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/frontend/src/features/authentication/Validations.jsx b/frontend/src/features/authentication/Validations.jsx
new file mode 100644
index 0000000..e8d642c
--- /dev/null
+++ b/frontend/src/features/authentication/Validations.jsx
@@ -0,0 +1,116 @@
+export const validateEmail = (newEmail) => {
+ if (newEmail === "") {
+ return "This is a required field";
+ } else if (!newEmail.includes("@")) {
+ return "Email should contain the '@' symbol";
+ } else {
+ return "";
+ }
+};
+
+export const validateFirstName = (newFirstName) => {
+ if (newFirstName.trim() === "") {
+ return "This is a required field";
+ } else {
+ return "";
+ }
+};
+
+export const validateLastName = (newLastName) => {
+ if (newLastName.trim() === "") {
+ return "This is a required field";
+ } else {
+ return "";
+ }
+};
+
+export const validatePassword = (newPassword, firstName, lastName, email) => {
+ const hasMinLength = newPassword.length >= 8
+ const hasUpperCase = /[A-Z]/.test(newPassword);
+ const hasLowerCase = /[a-z]/.test(newPassword);
+ const hasNumber = /\d/.test(newPassword);
+ const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/.test(newPassword);
+ const hasNoEmailFirstNameLastName = !newPassword.toLowerCase().includes(email.toLowerCase()) &&
+ !newPassword.toLowerCase().includes(firstName.toLowerCase()) &&
+ !newPassword.toLowerCase().includes(lastName.toLowerCase());
+
+ return {
+ hasMinLength,
+ hasUpperCase,
+ hasLowerCase,
+ hasNumber,
+ hasSpecialCharacter,
+ hasNoEmailFirstNameLastName
+ };
+};
+
+export const validateConfirmPassword = (newRePassword, password) => {
+ if (newRePassword !== password) {
+ return "Passwords do not match";
+ } else {
+ return "";
+ }
+};
+
+export const validateDate = (newDate) => {
+ if (!newDate.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+};
+export const validateGender = (newGender) => {
+ if (!newGender.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+};
+
+export const validateAddress = (newAddress) => {
+ if (!newAddress.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+};
+
+export const validateLanguage = (newLang) => {
+ if (!newLang.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+};
+
+export const validateCompany = (newCompany) => {
+ if (!newCompany.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+}
+
+export const validateJobTitle = (newJobTitle) => {
+ if (!newJobTitle.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+}
+
+export const validateIndustry = (newIndustry) => {
+ if (!newIndustry.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+}
+
+export const validateExperience = (newExperience) => {
+ if (!newExperience.trim()) {
+ return "This field is required";
+ } else {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/features/userProfile/profileGradient.jsx b/frontend/src/features/userProfile/profileGradient.jsx
new file mode 100644
index 0000000..16b3520
--- /dev/null
+++ b/frontend/src/features/userProfile/profileGradient.jsx
@@ -0,0 +1,21 @@
+export const generateGradient = (email) => {
+ if (!email) {
+ return null;
+ }
+
+ const gradients = [
+ "linear-gradient(135deg, #0061ff, #5596ff)",
+ "linear-gradient(135deg, #696eff, #9194ff)",
+ "linear-gradient(135deg, #ff5858, #ffc8c8)",
+ "linear-gradient(135deg, #30c67c, #82f4b1)",
+ "linear-gradient(135deg, #d3321d, #ffcf67)"
+ ];
+
+ // Create a hash from the email
+ const hash = email.split('').reduce((acc, char) => char.charCodeAt(0) + ((acc << 5) - acc), 0);
+
+ // Use the hash to get an index for the gradients array
+ const index = hash % gradients.length;
+
+ return gradients[index];
+}
diff --git a/frontend/src/hocs/Layout.jsx b/frontend/src/hocs/Layout.jsx
new file mode 100644
index 0000000..334eac8
--- /dev/null
+++ b/frontend/src/hocs/Layout.jsx
@@ -0,0 +1,18 @@
+import React, { useEffect } from 'react';
+import { connect } from 'react-redux';
+import { checkAuthenticated, load_user } from '../services/actions/auth';
+
+const Layout = ({ checkAuthenticated, load_user, children }) => {
+ useEffect(() => {
+ checkAuthenticated();
+ load_user();
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default connect(null, { checkAuthenticated, load_user })(Layout);
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 17df0e7..743e8fe 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -3,15 +3,28 @@
@tailwind utilities;
body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+
+::-webkit-scrollbar {
+ width: 8px; /* width of the entire scrollbar */
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #9ca3af; /* color of the scroll thumb */
+ border-radius: 20px; /* roundness of the scroll thumb */
+}
\ No newline at end of file
diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx
index d76f988..6e410fc 100644
--- a/frontend/src/index.jsx
+++ b/frontend/src/index.jsx
@@ -7,14 +7,17 @@ import './tailwind.css';
import {Provider} from "react-redux";
import {store} from "./app/store";
import 'react-tooltip/dist/react-tooltip.css'
+import {PreferencesProvider} from "./contexts/PreferencesContext";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
-
-
-
+
+
+
+
+
+
+
);
reportWebVitals();
diff --git a/frontend/src/layouts/MegaMenu.jsx b/frontend/src/layouts/MegaMenu.jsx
new file mode 100644
index 0000000..9e37dbe
--- /dev/null
+++ b/frontend/src/layouts/MegaMenu.jsx
@@ -0,0 +1,99 @@
+import React, { useState, useEffect } from "react";
+import "../assets/css/megamenu.css";
+import { ReactComponent as Logo } from "../assets/img/Schwarz-logo.svg";
+import { Link } from "react-router-dom";
+import { UserProfileDropdown } from "../components/UserProfileDropdown";
+import Notifications from "../assets/svg/notifications";
+import Menu from "../assets/svg/menu";
+
+export const MegaMenu = ({ classname, isAuthenticated, user }) => {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ const toggleMenu = () => {
+ setIsMenuOpen(!isMenuOpen);
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth >= 768) {
+ setIsMenuOpen(false);
+ }
+ };
+
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, []);
+
+ return (
+ <>
+
+ <>
+
+ >
+
+ >
+ );
+};
diff --git a/frontend/src/pages/EmailActivation/Activate.jsx b/frontend/src/pages/EmailActivation/Activate.jsx
new file mode 100644
index 0000000..d4bf4ab
--- /dev/null
+++ b/frontend/src/pages/EmailActivation/Activate.jsx
@@ -0,0 +1,38 @@
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+import {verify} from "../../services/actions/auth";
+import {useNavigate} from "react-router-dom";
+
+const Activate = ({ verify, match }) => {
+ const [verified, setVerified] = useState(false);
+ const navigate = useNavigate();
+
+ const verify_account = e => {
+ const uid = match.params.uid;
+ const token = match.params.token;
+
+ verify(uid, token);
+ setVerified(true);
+ };
+
+ if (verified) {
+ return navigate('/');
+ }
+
+ return (
+
+ );
+
+};
+
+export default connect(null, {verify})(Activate);
diff --git a/frontend/src/pages/EmailActivation/SendEmail.jsx b/frontend/src/pages/EmailActivation/SendEmail.jsx
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/pages/LandingScreen.jsx b/frontend/src/pages/LandingScreen.jsx
new file mode 100644
index 0000000..bf7b8d7
--- /dev/null
+++ b/frontend/src/pages/LandingScreen.jsx
@@ -0,0 +1,9 @@
+import {DialogBox} from "../components/DialogBox";
+import React from "react"
+
+export const LandingScreen = () => {
+ return(
+ <>
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Login/Login.jsx b/frontend/src/pages/Login/Login.jsx
deleted file mode 100644
index fa9b613..0000000
--- a/frontend/src/pages/Login/Login.jsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import {socialButtons} from "../../components/SocialButton";
-import InputField from "../../components/InputField";
-import React, {useState} from "react";
-import SubmitButton from "../../components/SubmitButton";
-import {motion} from "framer-motion";
-import {ReactComponent as LogoIcon} from "../../assets/img/Schwarz-logo.svg";
-import {useNavigate} from "react-router-dom";
-import {login} from "../../services/actions/auth";
-import {connect} from "react-redux";
-const LoginScreen = ({ login, isAuthenticated }) => {
- const [email, setEmail] = useState("");
- const [emailError, setEmailError] = useState("");
- const [password, setPassword] = useState("");
- const [passwordError, setPasswordError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
- const navigate = useNavigate();
-
- const handleEmailChange = (event) => {
- const newEmail = event.target.value;
- setEmail(newEmail);
-
- if (!newEmail.includes("@")) {
- setEmailError("Email should contain the '@' symbol");
- } else {
- setEmailError("");
- }
- };
-
- const handlePasswordChange = (event) => {
- const newPassword = event.target.value;
- setPassword(newPassword);
-
- if (newPassword.length < 8 || (newPassword.match(/\d/g) || []).length < 2) {
- setPasswordError(
- "Password should be at least 8 characters long and contain at least 2 numbers"
- );
- } else {
- setPasswordError("");
- }
- };
-
- const handleContinue1Click = e => {
- e.preventDefault();
-
- setShowErrors(true);
-
- // Check for errors and show them if present
- if (!email.includes("@")) {
- setEmailError("Email should contain the '@' symbol");
- } else {
-
- }
-
- login(email, password);
- };
-
- if (isAuthenticated) {
- return
- }
-
- // const continueWithGoogle = async () => {
- // try {
- // const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?redirect_uri=${process.env.REACT_APP_API_URL}/google`)
- //
- // window.location.replace(res.data.authorization_url);
- // } catch (err) {
- //
- // }
- // };
- //
- // const continueWithFacebook = async () => {
- // try {
- // const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/facebook/?redirect_uri=${process.env.REACT_APP_API_URL}/facebook`)
- //
- // window.location.replace(res.data.authorization_url);
- // } catch (err) {
- //
- // }
- // };
-
- if (isAuthenticated) {
- return navigate('/');
- }
-
- return (
-
-
-
-
-
Looping Simulation from Blender
-
-
- )
-}
-
-const mapStateToProps = state => ({
- isAuthenticated: state.auth.isAuthenticated
-});
-
-export default connect(mapStateToProps, { login })(LoginScreen);
\ No newline at end of file
diff --git a/frontend/src/pages/Login/LoginScreen.jsx b/frontend/src/pages/Login/LoginScreen.jsx
new file mode 100644
index 0000000..1f7b8e6
--- /dev/null
+++ b/frontend/src/pages/Login/LoginScreen.jsx
@@ -0,0 +1,114 @@
+import React, {useEffect, useState} from "react";
+import {motion} from "framer-motion";
+import {ReactComponent as LogoIcon} from "../../assets/img/Schwarz-logo.svg";
+import {useNavigate, useLocation} from "react-router-dom";
+import {login} from "../../services/actions/auth";
+import {connect} from "react-redux";
+import {Heading} from "../../components/Heading";
+import {SubHeading} from "../../components/SubHeading";
+import {LoginTab1} from "./components/LoginTab1";
+import SignupTab2 from "../Signup/components/SignupTab2";
+import {LoginTab2} from "./components/LoginTab2";
+import {Button} from "../../components/Button";
+
+const LoginScreen = ({login, isAuthenticated}) => {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const navigate = useNavigate();
+ const location = useLocation();
+ const from = location.state?.from?.pathname || '/';
+
+ const [showLoginTab1, setShowLoginTab1] = useState(true);
+ const [showLoginTab2, setShowLoginTab2] = useState(false);
+
+ useEffect(() => {
+ document.title = "Access your Schwarz ID | Schwarz"
+ }, [])
+
+ const handleTransition1 = () => {
+ setShowLoginTab1(false);
+ setShowLoginTab2(true);
+ };
+
+ // const continueWithGoogle = async () => {
+ // try {
+ // const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?redirect_uri=${process.env.REACT_APP_API_URL}/google`)
+ //
+ // window.location.replace(res.data.authorization_url);
+ // } catch (err) {
+ //
+ // }
+ // };
+ //
+ // const continueWithFacebook = async () => {
+ // try {
+ // const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/facebook/?redirect_uri=${process.env.REACT_APP_API_URL}/facebook`)
+ //
+ // window.location.replace(res.data.authorization_url);
+ // } catch (err) {
+ //
+ // }
+ // };
+
+ useEffect(() => {
+ if (isAuthenticated) {
+ navigate(from, { replace: true });
+ }
+ }, [isAuthenticated, navigate, from]);
+
+ return (
+
+
+
+
+
+
+
Looping Simulation from Blender
+
+
+ )
+}
+
+const mapStateToProps = state => ({
+ isAuthenticated: state.auth.isAuthenticated
+});
+
+export default connect(mapStateToProps, {login})(LoginScreen);
diff --git a/frontend/src/pages/Login/components/LoginTab1.jsx b/frontend/src/pages/Login/components/LoginTab1.jsx
new file mode 100644
index 0000000..c8825a2
--- /dev/null
+++ b/frontend/src/pages/Login/components/LoginTab1.jsx
@@ -0,0 +1,75 @@
+import React, {useState} from "react";
+import {validateEmail} from "../../../features/authentication/Validations";
+import {Button} from "../../../components/Button";
+import InputField from "../../../components/InputField";
+import {socialButtons} from "../../../components/SocialButton";
+
+export const LoginTab1 = ({onContinue, email, setEmail}) => {
+ const [emailError, setEmailError] = useState("");
+
+ const handleContinue1Click = (e) => {
+ e.preventDefault();
+
+ const val = validateEmail(email);
+
+ setEmailError(val);
+
+ if (val === "") {
+ onContinue();
+ }
+ };
+
+ return (
+
+
+ Sign
+ in
+
+ Sign
+ up
+
+
+
+
+ {socialButtons.map((button, index) => (
+
+
+ {button.icon} {button.text}
+
+
+ ))}
+
+ {/* section of ---- or ---- */}
+
+
+ or
+
+
+
+
+
+ {
+ const newEmail = event.target.value;
+ setEmail(newEmail);
+ const val = validateEmail(newEmail);
+ setEmailError(val);
+ }}
+ error={emailError}
+ />
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Login/components/LoginTab2.jsx b/frontend/src/pages/Login/components/LoginTab2.jsx
new file mode 100644
index 0000000..7d40a06
--- /dev/null
+++ b/frontend/src/pages/Login/components/LoginTab2.jsx
@@ -0,0 +1,44 @@
+import React, {useState} from "react";
+import InputField from "../../../components/InputField";
+import {Button} from "../../../components/Button";
+
+export const LoginTab2 = ({ login, email, password, setPassword }) => {
+ const [passwordError, setPasswordError] = useState("");
+
+ const handleSubmit = e => {
+ e.preventDefault();
+
+ const minLength = password.length >= 8;
+ if(!minLength)
+ setPasswordError("Password must have atleast 8 characters");
+ else {
+ setPasswordError("");
+ login(email, password);
+ }
+
+ };
+
+ return(
+
+
+ {
+ const newPassword = event.target.value;
+ setPassword(newPassword);
+ const minLength = password.length >= 8;
+ if(!minLength)
+ setPasswordError("Password must have atleast 8 characters");
+ else
+ setPasswordError("");
+ }}
+ error={passwordError}
+ />
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Login/testing.jsx b/frontend/src/pages/Login/testing.jsx
deleted file mode 100644
index 0ff27ea..0000000
--- a/frontend/src/pages/Login/testing.jsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import React, { useState } from "react";
-import "./Login.css";
-import { ReactComponent as LogoIcon } from "./schwarz.svg";
-import { ReactComponent as GoogleIcon } from "../../assets/img/google.svg";
-import { ReactComponent as TwitterIcon } from "../../assets/img/twitter.svg";
-import { ReactComponent as AppleIcon } from "../../assets/img/apple.svg";
-
-const socialButtons = [
- // ... (unchanged)
-];
-
-const LoginForm = () => {
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [emailError, setEmailError] = useState("");
- const [passwordError, setPasswordError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
- const [firstNameError, setFirstNameError] = useState("");
- const [lastNameError, setLastNameError] = useState("");
-
- const handleEmailChange = (event) => {
- const newEmail = event.target.value;
- setEmail(newEmail);
- setEmailError(newEmail.includes("@") ? "" : 'Email should contain "@" symbol');
- };
-
- const handlePasswordChange = (event) => {
- const newPassword = event.target.value;
- setPassword(newPassword);
- const numCount = (newPassword.match(/\d/g) || []).length;
- setPasswordError(
- newPassword.length >= 8 && numCount >= 2
- ? ""
- : "Password should be at least 8 characters long and contain at least 2 numbers"
- );
- };
-
- const handleNextStepClick = () => {
- // Validate fields and set errors
- setFirstNameError(firstName.trim() === "" ? "Please enter your first name" : "");
- setLastNameError(lastName.trim() === "" ? "Please enter your last name" : "");
- setEmailError(email.includes("@") ? "" : 'Email should contain "@" symbol');
- const numCount = (password.match(/\d/g) || []).length;
- setPasswordError(
- password.length >= 8 && numCount >= 2
- ? ""
- : "Password should be at least 8 characters long and contain at least 2 numbers"
- );
-
- // Set showErrors to true to display errors when the button is clicked
- setShowErrors(true);
- };
-
- return (
-
- {/* Main content */}
- {/* ... (unchanged) */}
-
- {/* Fields */}
-
- {/* First Name */}
-
-
First Name
-
{
- const newName = event.target.value;
- setFirstName(newName);
- setFirstNameError(newName.trim() === "" ? "Please enter your first name" : "");
- }}
- />
- {firstNameError && showErrors &&
{firstNameError}
}
-
-
- {/* Last Name */}
-
-
First Name
-
{
- const newName = event.target.value;
- setFirstName(newName);
- setFirstNameError(newName.trim() === "" ? "Please enter your first name" : "");
- }}
- />
- {firstNameError && showErrors &&
{firstNameError}
}
-
-
-
- {/* Pop-up */}
-
- {/* Add your pop-up content here */}
- {/* For example, a div with class "popup" */}
-
- {/* ... (popup content) */}
-
-
-
- );
-};
-
-export default LoginForm;
diff --git a/frontend/src/pages/Marketplace/MarketLanding.jsx b/frontend/src/pages/Marketplace/MarketLanding.jsx
new file mode 100644
index 0000000..886d6d7
--- /dev/null
+++ b/frontend/src/pages/Marketplace/MarketLanding.jsx
@@ -0,0 +1,95 @@
+import React, {useEffect, useState} from "react";
+import {MegaMenu} from "../../layouts/MegaMenu";
+import {Heading} from "../../components/Heading";
+import {HorizontalTabsBar} from "../../components/HorizontalTabsBar";
+import Model from "../../assets/svg/model";
+import Texture from "../../assets/svg/texture";
+import HDRI from "../../assets/svg/hdri";
+import Rig from "../../assets/svg/rig";
+import Addon from "../../assets/svg/addon";
+import SearchBar from "../../components/SearchBar";
+import {ModelsPage} from "./components/ModelsPage";
+import {TexturesPage} from "./components/TexturesPage";
+import {HDRIsPage} from "./components/HDRIsPage";
+import {RigsPage} from "./components/RigsPage";
+import {AddonsPage} from "./components/AddonsPage";
+import {AnimatePresence, motion} from "framer-motion";
+import {SubHeading} from "../../components/SubHeading";
+import {connect} from "react-redux";
+import '../../assets/css/tabsbar.css'
+
+const Store = ({ isAuthenticated, user }) => {
+ const [activeTab, setActiveTab] = useState(0);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ setLoading(false);
+ }, []);
+
+ const tabs = [
+ { text: "Models", tagline:"Explore a collection of stable, high-quality 3D models in a variety of file formats " +
+ "for every project.", icon: },
+ { text: "Textures", tagline:"Transform your creations with premium textures, available in all major formats to " +
+ "suit your needs.", icon: },
+ { text: "HDRIs", tagline:"Illuminate your scenes with stunning HDRI environments, compatible with a wide range " +
+ "of 3D softwares.", icon: },
+ { text: "Rigs", tagline:"Animate with ease using versatile and robust rigs, designed for seamless integration " +
+ "in any platform.", icon: },
+ { text: "Addons", tagline:"Enhance your toolkit with powerful, reliable addons available for the most popular 3D " +
+ "softwares for 3D design.", icon: },
+ ];
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {tabs[activeTab].text === 'Models' &&
}
+ {tabs[activeTab].text === 'Textures' &&
}
+ {tabs[activeTab].text === 'HDRIs' &&
}
+ {tabs[activeTab].text === 'Rigs' &&
}
+ {tabs[activeTab].text === 'Addons' &&
}
+
+ >
+ );
+};
+
+const mapStateToProps = state => ({
+ isAuthenticated: state.auth.isAuthenticated,
+ user: state.auth.user
+});
+
+export default connect(mapStateToProps)(Store);
diff --git a/frontend/src/pages/Marketplace/components/AddonsPage.jsx b/frontend/src/pages/Marketplace/components/AddonsPage.jsx
new file mode 100644
index 0000000..726041e
--- /dev/null
+++ b/frontend/src/pages/Marketplace/components/AddonsPage.jsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const AddonsPage = () => {
+
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Marketplace/components/HDRIsPage.jsx b/frontend/src/pages/Marketplace/components/HDRIsPage.jsx
new file mode 100644
index 0000000..ca2809f
--- /dev/null
+++ b/frontend/src/pages/Marketplace/components/HDRIsPage.jsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const HDRIsPage = () => {
+
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Marketplace/components/ModelsPage.jsx b/frontend/src/pages/Marketplace/components/ModelsPage.jsx
new file mode 100644
index 0000000..e5ecc0d
--- /dev/null
+++ b/frontend/src/pages/Marketplace/components/ModelsPage.jsx
@@ -0,0 +1,34 @@
+import React, {useState} from "react";
+import {Heading} from "../../../components/Heading";
+import Filter from "../../../assets/svg/filter";
+import Sidebar from "../../../components/Sidebar";
+
+export const ModelsPage = () => {
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
+ const toggleSidebar = () => {
+ setIsSidebarOpen(!isSidebarOpen);
+ };
+
+ const closeSidebar = () => {
+ setIsSidebarOpen(false);
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Marketplace/components/RigsPage.jsx b/frontend/src/pages/Marketplace/components/RigsPage.jsx
new file mode 100644
index 0000000..eeeb06d
--- /dev/null
+++ b/frontend/src/pages/Marketplace/components/RigsPage.jsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const RigsPage = () => {
+
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Marketplace/components/TexturesPage.jsx b/frontend/src/pages/Marketplace/components/TexturesPage.jsx
new file mode 100644
index 0000000..076e101
--- /dev/null
+++ b/frontend/src/pages/Marketplace/components/TexturesPage.jsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const TexturesPage = () => {
+
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Signup/SignupScreen.jsx b/frontend/src/pages/Signup/SignupScreen.jsx
index bcc6c17..cd99d29 100644
--- a/frontend/src/pages/Signup/SignupScreen.jsx
+++ b/frontend/src/pages/Signup/SignupScreen.jsx
@@ -1,4 +1,4 @@
-import React, {useState} from "react";
+import React, {useEffect, useState} from "react";
import {Link, useNavigate} from "react-router-dom";
import {motion} from "framer-motion"
import {ReactComponent as LogoIcon} from "../../assets/img/Schwarz-logo.svg";
@@ -6,17 +6,38 @@ import SignupTab1 from "./components/SignupTab1";
import SignupTab2 from "./components/SignupTab2";
import SignupTab3 from "./components/SignupTab3";
import SignupTab4 from "./components/SignupTab4";
-import {useRegisterUserMutation} from "../../services/userAuthApi";
+import {signup} from "../../services/actions/auth";
+import {connect} from "react-redux";
+import {Button} from "../../components/Button";
+import {Heading} from "../../components/Heading";
+import {SubHeading} from "../../components/SubHeading";
+
+const SignupScreen = ({signup, isAuthenticated}) => {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [re_password, setRePassword] = useState("");
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [date, setDate] = useState("");
+ const [gender, setGender] = useState("Man");
+ const [address, setAddress] = useState("");
+ const [preferred_lang, setLanguage] = useState("English");
+ const [company, setCompany] = useState("");
+ const [job_title, setJobTitle] = useState("");
+ const [industry, setIndustry] = useState("");
+ const [experience, setExperience] = useState("");
-const SignupScreen = () => {
const [showSignupTab1, setShowSignupTab1] = useState(true);
const [showSignupTab2, setShowSignupTab2] = useState(false);
const [showSignupTab3, setShowSignupTab3] = useState(false);
const [showSignupTab4, setShowSignupTab4] = useState(false);
- const [server_error, setServerError] = useState({})
+ const [accountCreated, setAccountCreated] = useState(false);
const navigate = useNavigate();
- const [registerUser, {isLoading}] = useRegisterUserMutation()
+
+ useEffect(() => {
+ document.title = "Create a Schwarz ID | Schwarz"
+ }, [])
const handleTransition1 = () => {
setShowSignupTab1(false);
@@ -42,102 +63,152 @@ const SignupScreen = () => {
setShowSignupTab3(true)
}
+ const handleSubmit = e => {
+ e.preventDefault();
+
+ if (password === re_password) {
+ signup(email, firstName, lastName, date, gender, address, preferred_lang, company, job_title, industry, experience, password, re_password);
+ setAccountCreated(true);
+ }
+ };
+
+ if (isAuthenticated) {
+ return navigate('/');
+ }
+ if (accountCreated) {
+ return navigate('/login');
+ }
+
return (
-
+
-
+
Looping Simulation from Blender
);
};
-export default SignupScreen;
+const mapStateToProps = state => ({
+ isAuthenticated: state.auth.isAuthenticated
+});
+
+export default connect(mapStateToProps, {signup})(SignupScreen);
+
diff --git a/frontend/src/pages/Signup/components/SignupTab1.jsx b/frontend/src/pages/Signup/components/SignupTab1.jsx
index da110bc..b05dcb6 100644
--- a/frontend/src/pages/Signup/components/SignupTab1.jsx
+++ b/frontend/src/pages/Signup/components/SignupTab1.jsx
@@ -1,30 +1,20 @@
import {socialButtons} from "../../../components/SocialButton";
import InputField from "../../../components/InputField";
import React, {useState} from "react";
-import SubmitButton from "../../../components/SubmitButton";
+import {Button} from "../../../components/Button";
+import {validateEmail} from "../../../features/authentication/Validations";
-const SignupTab1 = ({onContinue}) => {
- const [email, setEmail] = useState("");
+const SignupTab1 = ({onContinue, email, setEmail}) => {
const [emailError, setEmailError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
- const handleEmailChange = (event) => {
- const newEmail = event.target.value;
- setEmail(newEmail);
- if (!newEmail.includes("@")) {
- setEmailError("Email should contain the '@' symbol");
- } else {
- setEmailError("");
- }
- };
+ const handleContinue1Click = (e) => {
+ e.preventDefault();
+
+ const val = validateEmail(email);
- const handleContinue1Click = () => {
- setShowErrors(true);
+ setEmailError(val);
- // Check for errors and show them if present
- if (!email.includes("@")) {
- setEmailError("Email should contain the '@' symbol");
- } else {
+ if(val === "") {
onContinue();
}
};
@@ -32,9 +22,9 @@ const SignupTab1 = ({onContinue}) => {
return (
+ className="relative align-center w-[24vw] max-md:w-[60vw] bg-neutral-200 mb-[32px] h-14 grid grid-cols-2 gap-2 p-1 rounded-md">
Sign
+ className="text-neutral-500 hover:text-black hover:shadow-inner-2xl font-bold rounded-lg transition-all ease-in-out duration-250 outline-none">Sign
in
Sign up
@@ -49,7 +39,6 @@ const SignupTab1 = ({onContinue}) => {
))}
- {/* section of ---- or ---- */}
or
@@ -60,18 +49,22 @@ const SignupTab1 = ({onContinue}) => {
{
+ const newEmail = event.target.value;
+ setEmail(newEmail);
+ const val = validateEmail(newEmail);
+ setEmailError(val);
+ }}
error={emailError}
- showError={showErrors}
/>
-
+
)
diff --git a/frontend/src/pages/Signup/components/SignupTab2.jsx b/frontend/src/pages/Signup/components/SignupTab2.jsx
index 4e89225..eb650a0 100644
--- a/frontend/src/pages/Signup/components/SignupTab2.jsx
+++ b/frontend/src/pages/Signup/components/SignupTab2.jsx
@@ -1,68 +1,59 @@
import InputField from "../../../components/InputField";
import React, {useState} from "react";
-import SubmitButton from "../../../components/SubmitButton";
-
-const SignupTab2 = ({onContinue}) => {
- const [password, setPassword] = useState("");
- const [confirmPassword, setConfirmPassword] = useState("");
- const [passwordError, setPasswordError] = useState("");
+import {Button} from "../../../components/Button";
+import {
+ validateConfirmPassword,
+ validateFirstName,
+ validateLastName,
+ validatePassword
+} from "../../../features/authentication/Validations";
+import {DialogBox} from "../../../components/DialogBox";
+import Check from "../../../assets/svg/check";
+import Cross from "../../../assets/svg/cross"
+
+const SignupTab2 = ({
+ onContinue,
+ email,
+ password,
+ setPassword,
+ re_password,
+ setRePassword,
+ firstName,
+ setFirstName,
+ lastName,
+ setLastName
+ }) => {
+ const [passwordError, setPasswordError] = useState({
+ hasMinLength: false,
+ hasUpperCase: false,
+ hasLowerCase: false,
+ hasNumber: false,
+ hasSpecialCharacter: false,
+ hasNoEmailFirstNameLastName: false
+ });
const [confirmPasswordError, setConfirmPasswordError] = useState("");
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
const [firstNameError, setFirstNameError] = useState("");
const [lastNameError, setLastNameError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
-
- const handlePasswordChange = (event) => {
- const newPassword = event.target.value;
- setPassword(newPassword);
-
- if (newPassword.length < 8 || (newPassword.match(/\d/g) || []).length < 2) {
- setPasswordError(
- "Password should be at least 8 characters long and contain at least 2 numbers"
- );
- } else {
- setPasswordError("");
- }
- };
-
- const handleConfirmPasswordChange = (event) => {
- const newConfirmPassword = event.target.value;
- setConfirmPassword(newConfirmPassword);
-
- if (newConfirmPassword.length < 8 || (newConfirmPassword.match(/\d/g) || []).length < 2) {
- setConfirmPasswordError(
- "Password should be at least 8 characters long and contain at least 2 numbers"
- );
- } else {
- setConfirmPasswordError("");
- }
- };
+ const [showDialog, setShowDialog] = useState(false);
- const handleContinue2Click = () => {
- setShowErrors(true);
+ const handleContinue2Click = (e) => {
+ e.preventDefault();
- if (password.length < 8 || (password.match(/\d/g) || []).length < 2) {
- setPasswordError(
- "Password should be at least 8 characters long and contain at least 2 numbers"
- );
- } else {
+ const err1 = validateFirstName(firstName);
+ const err2 = validateLastName(lastName);
+ const { hasMinLength, hasUpperCase, hasLowerCase, hasNumber, hasSpecialCharacter, hasNoEmailFirstNameLastName } = validatePassword(password, firstName, lastName, email);
+ const err4 = validateConfirmPassword(re_password, password);
- }
+ setFirstNameError(err1);
+ setLastNameError(err2);
+ setConfirmPasswordError(err4);
- if (firstName.trim() === "") {
- setFirstNameError("Please enter your first name");
+ if(err1 === "" && err2 === "" && (hasMinLength && hasLowerCase && hasUpperCase && hasNumber && hasSpecialCharacter
+ && hasNoEmailFirstNameLastName) && err4 === "") {
+ onContinue();
} else {
- setFirstNameError("");
+ setShowDialog(true);
}
-
- if (lastName.trim() === "") {
- setLastNameError("Please enter your last name");
- } else {
- setLastNameError("");
- }
-
- onContinue();
};
return (
@@ -73,9 +64,13 @@ const SignupTab2 = ({onContinue}) => {
type="text"
placeholder="Enter your first name"
value={firstName}
- onChange={(event) => setFirstName(event.target.value)}
+ onChange={(event) => {
+ const newFirstName = event.target.value;
+ setFirstName(newFirstName);
+ const err= validateFirstName(newFirstName);
+ setFirstNameError(err);
+ }}
error={firstNameError}
- showError={showErrors}
/>
{
type="text"
placeholder="Enter your last name"
value={lastName}
- onChange={(event) => setLastName(event.target.value)}
+ onChange={(event) => {
+ const newLastName = event.target.value;
+ setLastName(newLastName);
+ const err = validateLastName(newLastName);
+ setLastNameError(err);
+ }}
error={lastNameError}
- showError={showErrors}
/>
{
type="password"
placeholder="Enter your password"
value={password}
- onChange={handlePasswordChange}
- error={passwordError}
- showError={showErrors}
+ onChange={(event) => {
+ const newPassword = event.target.value;
+ setPassword(newPassword);
+ const { hasMinLength, hasUpperCase, hasLowerCase, hasNumber, hasSpecialCharacter, hasNoEmailFirstNameLastName } = validatePassword(newPassword, firstName, lastName, email);
+ setPasswordError({ hasMinLength, hasUpperCase, hasLowerCase, hasNumber, hasSpecialCharacter, hasNoEmailFirstNameLastName });
+ }}
+ onFocus={() => setShowDialog(true)}
+ onBlur={() => setShowDialog(false)}
/>
+ {showDialog &&
+
+ {passwordError.hasMinLength ?
+
:
+ }
+ Has atleast 8 characters
+
+
+ {passwordError.hasLowerCase ?
+
:
+ }
+ Has atleast 1 lowercase letter
+
+
+ {passwordError.hasUpperCase ?
+
:
+ }
+ Has atleast 1 uppercase letter
+
+
+ {passwordError.hasNumber ?
+
:
+ }
+ Has atleast 1 number
+
+
+ {passwordError.hasSpecialCharacter ?
+
:
+ }
+ Has atleast 1 special character
+
+
+ {passwordError.hasNoEmailFirstNameLastName ?
+
:
+ }
+ Does not contain your email, first name or last name
+
+ >
+ } classname={"text-sm top-10 -right-16 w-96"}/>}
+
{
+ const newRePassword = event.target.value;
+ setRePassword(newRePassword);
+ const err = validateConfirmPassword(newRePassword, password);
+ setConfirmPasswordError(err);
+ }}
error={confirmPasswordError}
- showError={showErrors}
/>
-
+
)
diff --git a/frontend/src/pages/Signup/components/SignupTab3.jsx b/frontend/src/pages/Signup/components/SignupTab3.jsx
index 8bc3bca..b9f4308 100644
--- a/frontend/src/pages/Signup/components/SignupTab3.jsx
+++ b/frontend/src/pages/Signup/components/SignupTab3.jsx
@@ -1,46 +1,80 @@
import React, {useState} from "react";
import InputField from "../../../components/InputField";
-import SubmitButton from "../../../components/SubmitButton";
+import {Button} from "../../../components/Button";
import SelectField from "../../../components/SelectField";
+import {
+ validateAddress,
+ validateDate,
+ validateGender,
+ validateLanguage
+} from "../../../features/authentication/Validations";
-const SignupTab3 = ({onPrevious, onContinue}) => {
- const [age, setAge] = useState("");
- const [ageError, setAgeError] = useState("");
- const [gender, setGender] = useState("");
+const SignupTab3 = ({
+ onPrevious,
+ onContinue,
+ date,
+ setDate,
+ gender,
+ setGender,
+ address,
+ setAddress,
+ preferred_lang,
+ setLanguage
+ }) => {
+ const [dateError, setDateError] = useState("");
const [genderError, setGenderError] = useState("");
- const [address, setAddress] = useState("");
const [addressError, setAddressError] = useState("");
- const [language, setLanguage] = useState("");
const [languageError, setLanguageError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
const handlePrevious2Click = () => {
onPrevious();
}
- const handleContinue3Click = () => {
- onContinue();
+ const handleContinue3Click = (e) => {
+ e.preventDefault();
+
+ const err1 = validateDate(date);
+ const err2 = validateGender(gender);
+ const err3 = validateAddress(address);
+ const err4 = validateLanguage(preferred_lang);
+
+ setDateError(err1);
+ setGenderError(err2);
+ setAddressError(err3);
+ setLanguageError(err4);
+
+ if(err1 === "" && err2 === "" && err3 === "" && err4 === "") {
+ onContinue();
+ }
}
return (
setAge(event.target.value)}
- error={ageError}
- showError={showErrors}
+ label="Date of Birth"
+ type="date"
+ value={date}
+ onChange={(event) => {
+ const newDate = event.target.value;
+ setDate(newDate);
+ const err = validateDate(newDate);
+ setDateError(err)
+ }
+ }
+ error={dateError}
/>
setGender(event.target.value)}
+ onChange={(event) => {
+ const newGender = event.target.value;
+ setGender(newGender);
+ const err = validateGender(newGender);
+ setGenderError(err);
+ }}
error={genderError}
- showError={showErrors}
options={["Man", "Woman", "Transgender", "Non-binary/non-conforming", "Prefer not to respond"]}
defaultOption={"Male"}
/>
@@ -50,17 +84,25 @@ const SignupTab3 = ({onPrevious, onContinue}) => {
type="text"
placeholder="Enter your permanent address"
value={address}
- onChange={(event) => setAddress(event.target.value)}
+ onChange={(event) => {
+ const newAddress = event.target.value;
+ setAddress(newAddress);
+ const err = validateAddress(newAddress);
+ setAddressError(err);
+ }}
error={addressError}
- showError={showErrors}
/>
setLanguage(event.target.value)}
+ value={preferred_lang}
+ onChange={(event) => {
+ const newLang = event.target.value;
+ setLanguage(newLang);
+ const err = validateLanguage(newLang);
+ setLanguageError(err);
+ }}
error={languageError}
- showError={showErrors}
options={[
"Afrikaans",
"Albanian",
@@ -205,15 +247,24 @@ const SignupTab3 = ({onPrevious, onContinue}) => {
"Yoruba",
"Zulu"
]}
- defaultOption={"English"}
/>
-
);
diff --git a/frontend/src/pages/Signup/components/SignupTab4.jsx b/frontend/src/pages/Signup/components/SignupTab4.jsx
index 1e38ee0..34ecd64 100644
--- a/frontend/src/pages/Signup/components/SignupTab4.jsx
+++ b/frontend/src/pages/Signup/components/SignupTab4.jsx
@@ -1,48 +1,55 @@
import React, {useState} from "react";
import InputField from "../../../components/InputField";
import SelectField from "../../../components/SelectField";
-import SubmitButton from "../../../components/SubmitButton";
+import {Button} from "../../../components/Button";
+import {
+ validateCompany,
+ validateExperience,
+ validateIndustry,
+ validateJobTitle
+} from "../../../features/authentication/Validations";
+import {signup} from "../../../services/actions/auth";
-const SignupTab4 = ({onPrevious}) => {
- const [company, setCompany] = useState("");
+const SignupTab4 = ({onPrevious, company, setCompany, job_title, setJobTitle, industry, setIndustry, experience, setExperience, email, firstName, lastName, password, re_password, date, gender, address, preferred_lang}) => {
const [companyError, setCompanyError] = useState("");
- const [position, setPosition] = useState("");
- const [positionError, setPositionError] = useState("");
- const [industry, setIndustry] = useState("");
+ const [jobTitleError, setJobTitleError] = useState("");
const [industryError, setIndustryError] = useState("");
- const [years, setYears] = useState("");
- const [yearsError, setYearsError] = useState("");
- const [showErrors, setShowErrors] = useState(false);
+ const [experienceError, setExperienceError] = useState("");
- const handlePrevious3Click = () => {
+ const handlePrevious3Click = (e) => {
+ e.preventDefault();
onPrevious();
}
- const handleSubmit = () => {
-
- }
-
return (
setCompany(event.target.value)}
+ onChange={(event) => {
+ const newCompany = event.target.value;
+ setCompany(newCompany);
+ const err = validateCompany(newCompany);
+ setCompanyError(err);
+ }}
error={companyError}
- showError={showErrors}
/>
setPosition(event.target.value)}
- error={positionError}
- showError={showErrors}
+ placeholder="Enter your job title"
+ value={job_title}
+ onChange={(event) => {
+ const newJobTitle = event.target.value;
+ setJobTitle(newJobTitle);
+ const err = validateJobTitle(newJobTitle);
+ setJobTitleError(err);
+ }}
+ error={jobTitleError}
/>
{
type="text"
placeholder="Enter your industry"
value={industry}
- onChange={(event) => setIndustry(event.target.value)}
+ onChange={(event) => {
+ const newIndustry = event.target.value;
+ setIndustry(newIndustry);
+ const err = validateIndustry(newIndustry);
+ setIndustryError(err);
+ }}
error={industryError}
- showError={showErrors}
/>
setYears(event.target.value)}
- error={yearsError}
- showError={showErrors}
+ value={experience}
+ onChange={(event) => {
+ const newExperience = event.target.value;
+ setExperience(newExperience);
+ const err = validateExperience(newExperience);
+ setExperienceError(err);
+ }}
+ error={experienceError}
/>
-
-
-
-
+
+
);
}
diff --git a/frontend/src/pages/TwoFa/Otpscreen.jsx b/frontend/src/pages/TwoFa/Otpscreen.jsx
index aceafd1..72dfb70 100644
--- a/frontend/src/pages/TwoFa/Otpscreen.jsx
+++ b/frontend/src/pages/TwoFa/Otpscreen.jsx
@@ -8,12 +8,12 @@ import {Link} from "react-router-dom";
const Otpscreen = () => {
return (
-
+
-
Authenticate your Account
+
Authenticate your Account
Enter the 6 digit code generated by your Google Authenticator app to verify
your identity
diff --git a/frontend/src/pages/TwoFa/TwofaOptions.jsx b/frontend/src/pages/TwoFa/TwofaOptions.jsx
index ba4d812..0139c93 100644
--- a/frontend/src/pages/TwoFa/TwofaOptions.jsx
+++ b/frontend/src/pages/TwoFa/TwofaOptions.jsx
@@ -1,19 +1,29 @@
-import React, {useState} from "react";
-import SubmitButton from "../../components/SubmitButton";
+import React from "react";
+import {Button} from "../../components/Button";
+import {SubHeading} from "../../components/SubHeading";
+import {Heading} from "../../components/Heading";
+import {ReactComponent as LogoIcon} from "../../assets/img/Schwarz-logo.svg";
const TwofaForm = () => {
return(
-
Your 2FA Methods
-
Choose from the available 2FA
- Methods to log in to your Schwarz ID
-
+
+
+
+
+
+
-
Email Authentication
-
Low Security
-
A
+
Email Authentication
+
Low Security
+
A
verification code will be sent to your registered
email id
@@ -23,11 +33,11 @@ const TwofaForm = () => {
-
+
-
Text Authentication
-
Medium Security
-
A verification code will be sent to your registered phone
+
Text Authentication
+
Medium Security
+
A verification code will be sent to your registered phone
number
@@ -36,11 +46,11 @@ const TwofaForm = () => {
-
+
-
Mobile Authenticator Application
-
Highest Security
-
Verify your identity with the Google Authenticator
+
Mobile Authenticator Application
+
Highest Security
+
Verify your identity with the Google Authenticator
application
@@ -50,11 +60,11 @@ const TwofaForm = () => {
- {window.location.href = "/"}} isStep={true}/>
+ {window.location.href = "/"}} isStep={true}/>
-
+
Looping Simulation from Blender
diff --git a/frontend/src/pages/UserAccount/UserAccount.jsx b/frontend/src/pages/UserAccount/UserAccount.jsx
new file mode 100644
index 0000000..360beff
--- /dev/null
+++ b/frontend/src/pages/UserAccount/UserAccount.jsx
@@ -0,0 +1,81 @@
+import React, {useEffect, useState} from 'react';
+import {connect} from "react-redux";
+import {useNavigate} from "react-router-dom";
+import Texture from "../../assets/svg/texture";
+import {MegaMenu} from "../../layouts/MegaMenu";
+import Account from "../../assets/svg/account";
+import {VerticalTabsBar} from "../../components/VerticalTabsBar";
+import {AccountDetails} from "./components/AccountDetails";
+import OpenInNew from "../../assets/svg/openinnew";
+
+const UserAccount = ({isAuthenticated, user}) => {
+ const navigate = useNavigate();
+ const [activeSubTab, setActiveSubTab] = useState([0,0]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ if (user !== null) {
+ setLoading(false);
+ }
+ }, [user]);
+
+
+ const tabs = [
+ {
+ text: "General",
+ subtabs: [
+ {
+ text: "Basic Account Details",
+ icon:
+ },
+ // {
+ // text: "Account Details",
+ // icon:
+ // }
+ ]
+ },
+ {
+ text: "More",
+ subtabs: [
+ {
+ text: "Artist Profile",
+ icon:
+ },
+ // {
+ // text: "Account Details",
+ // icon:
+ // }
+ ]
+ },
+ ];
+
+ if (!isAuthenticated) {
+ return navigate('/login', { state: { from: '/account' } });
+ }
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return (
+
+
+
+
+
+
+
+ {(activeSubTab[0] === 0 && activeSubTab[1] === 0) &&
}
+
+
+
+ );
+}
+
+const mapStateToProps = state => ({
+ isAuthenticated: state.auth.isAuthenticated,
+ user: state.auth.user
+});
+
+export default connect(mapStateToProps)(UserAccount);
diff --git a/frontend/src/pages/UserAccount/components/AccountDetails.jsx b/frontend/src/pages/UserAccount/components/AccountDetails.jsx
new file mode 100644
index 0000000..87c3ba7
--- /dev/null
+++ b/frontend/src/pages/UserAccount/components/AccountDetails.jsx
@@ -0,0 +1,79 @@
+import React, {useState} from 'react';
+import InputField from "../../../components/InputField";
+import {Button} from "../../../components/Button";
+
+export const AccountDetails = ({user}) => {
+ const [isEditBasic, setIsEditBasic] = useState(true);
+ const [firstName, setFirstName] = useState(user.first_name);
+ const [lastName, setLastName] = useState(user.last_name);
+ const [email, setEmail] = useState(user.email);
+
+ return (
+
+
+
Basic Account Details
+
+
{
+ setIsEditBasic(!isEditBasic);
+ }}>Edit
+
+
+
+
+
+
+
+
+ {
+ const newFirstName = event.target.value;
+ setFirstName(newFirstName);
+ }}
+ classname={"w-1/2"}
+ />
+
+ {
+ const newLastName = event.target.value;
+ setLastName(newLastName);
+ }}
+ classname={"w-1/2"}
+ />
+
+
{
+ const newEmail = event.target.value;
+ setEmail(newEmail);
+ }}
+ />
+
+ Personal Information
+
+
+
+
+
+
+
+ Work Information
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/profile/Profile.jsx b/frontend/src/pages/profile/Profile.jsx
deleted file mode 100644
index 4d75465..0000000
--- a/frontend/src/pages/profile/Profile.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from "react";
-
-import {
- Button,
- Img,
- Input,
- Line,
- List,
- SelectBox,
- Text,
- TextArea,
-} from "../../components";
-import InputField from "../../components/InputField";
-import { IoSearch } from "react-icons/io5";
-import { CloseSVG } from "./../../assets/svg/close";
-
-const userTwoOptionsList = [
- { label: "User", value: "User" },
- { label: "Artist", value: "Artist" },
- { label: "Admin", value: "Admin" },
-];
-
-export default function Profile() {
- const [searchinputvalue, setSearchinputvalue] = React.useState("");
-
- return (
-
-
-
-
-
-
setSearchinputvalue(e)}
- className="!placeholder:text-gray-500 !text-gray-500 px-10 text-left text-sm tracking-[0.14px] w-[120%] "
- wrapClassName="border border-gray-300 border-solid flex md:flex-1 md:mt-0 my-0.5 md:w-full"
- prefix={
-
- }
- shape="round"
- size="md"
- />
-
-
-
-
-
-
-
-
-
- }
- isMulti={false}
- name="user Two"
- isSearchable={false}
- options={userTwoOptionsList}
- />
-
-
-
-
-
- );
-}
diff --git a/frontend/src/pages/profile/settings.jsx b/frontend/src/pages/profile/settings.jsx
deleted file mode 100644
index a35db62..0000000
--- a/frontend/src/pages/profile/settings.jsx
+++ /dev/null
@@ -1,560 +0,0 @@
-import React from "react";
-
-// import { Sidebar } from "react-pro-sidebar";
-// import { useNavigate } from "react-router-dom";
-
-import {
- Button,
- Img,
- Input,
- Line,
- List,
- SelectBox,
- Text,
- TextArea,
-} from "../../components";
-import InputField from "../../components/InputField";
-
-import { CloseSVG } from "./../../assets/svg/close";
-
-const userTwoOptionsList = [
- { label: "Option1", value: "option1" },
- { label: "Option2", value: "option2" },
- { label: "Option3", value: "option3" },
-];
-
-
-export default function SettingsPage () {
-// const navigate = useNavigate();
-
- const [searchinputvalue, setSearchinputvalue] = React.useState("");
-
- return (
-
-
-
-
-
-
-
- setSearchinputvalue(e)}
- className="!placeholder:text-gray-500 !text-gray-500 p-0 text-left text-sm tracking-[0.14px] w-full"
- wrapClassName="border border-gray-300 border-solid flex md:flex-1 md:mt-0 my-0.5 md:w-full"
- prefix={
-
- }
-
- shape="round"
- size="md"
- />
-
-
- }
- shape="round"
- color="gray_100"
- size="xl"
- variant="outline"
- >
-
- 3,421 ETH
-
-
-
-
-
-
- }
- isMulti={false}
- name="user Two"
- isSearchable={false}
- options={userTwoOptionsList}
- />
-
-
-
-
-
-
- General Settings
-
-
- Update your photo and personal details here.
-
-
-
-
- Every changes automaticly saved
-
-
-
- Preview
-
-
- Cancel
-
-
-
-
-
-
-
-
- Personal Information
-
-
-
-
-
-
-
-
-
- First Name
-
-
- *
-
-
-
- }
- color="gray_400"
- size="md"
- variant="outline"
- />
-
-
-
-
- Last Name
-
-
- *
-
-
-
-
-
-
-
-
- Email Address
-
-
- *
-
-
-
- }
- suffix={
-
- }
- color="gray_400"
- size="md"
- variant="outline"
- />
-
-
-
- Username
-
-
- }
- suffix={
-
- }
- color="gray_400"
- variant="outline"
- />
-
-
- Biography
-
-
-
-
-
-
- Website
-
-
- }
- color="gray_400"
- size="md"
- variant="outline"
- />
-
-
-
-
-
-
- Notification Settings
-
-
-
-
-
-
-
- Item Sold
-
-
- When someone purchased one of your items
-
-
-
-
-
-
-
-
- Successful Purchase
-
-
- When you successfully buy an item
-
-
-
-
-
-
-
-
- Auction Expirates
-
-
- When a timed auction you created ends
-
-
-
-
-
-
-
-
- Owned Item Updates
-
-
- When a significant update occurs for one of the items
- you have purchased on Enefthy
-
-
-
-
-
-
-
-
-
-
-
- Your Photo
-
-
-
-
-
-
-
-
- Edit your photo
-
-
-
- Delete
-
-
- Update
-
-
-
-
-
-
-
-
-
-
- Click to upload or drag and drop
-
-
- <>
- PNG, JPG or Gif
-
- Max 20Mb
- >
-
-
-
-
-
-
-
-
- Social Linked
-
-
-
-
-
-
-
- Connected
-
-
-
-
- Edit your photo
-
-
- <>
- Use Google to sign in to your account.
-
- Click here to learn more.
- >
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-
-
-
diff --git a/frontend/src/services/actions/auth.js b/frontend/src/services/actions/auth.js
index 804515d..afcc0ef 100644
--- a/frontend/src/services/actions/auth.js
+++ b/frontend/src/services/actions/auth.js
@@ -32,8 +32,7 @@ export const load_user = () => async dispatch => {
};
try {
- const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
-
+ const res = await axios.get(`${import.meta.env.VITE_REACT_APP_API_URL}/api/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
@@ -66,7 +65,7 @@ export const googleAuthenticate = (state, code) => async dispatch => {
const formBody = Object.keys(details).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(details[key])).join('&');
try {
- const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?${formBody}`, config);
+ const res = await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/o/google-oauth2/?${formBody}`, config);
dispatch({
type: GOOGLE_AUTH_SUCCESS,
@@ -98,7 +97,7 @@ export const facebookAuthenticate = (state, code) => async dispatch => {
const formBody = Object.keys(details).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(details[key])).join('&');
try {
- const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/o/facebook/?${formBody}`, config);
+ const res = await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/o/facebook/?${formBody}`, config);
dispatch({
type: FACEBOOK_AUTH_SUCCESS,
@@ -126,7 +125,7 @@ export const checkAuthenticated = () => async dispatch => {
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
- const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config)
+ const res = await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/jwt/verify/`, body, config)
if (res.data.code !== 'token_not_valid') {
dispatch({
@@ -160,7 +159,7 @@ export const login = (email, password) => async dispatch => {
const body = JSON.stringify({ email, password });
try {
- const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
+ const res = await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
@@ -175,17 +174,17 @@ export const login = (email, password) => async dispatch => {
}
};
-export const signup = (first_name, last_name, email, password, re_password) => async dispatch => {
+export const signup = (email, first_name, last_name, date_of_birth, gender, address, preferred_lang, company, job_title, industry, experience, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
- const body = JSON.stringify({ first_name, last_name, email, password, re_password });
+ const body = JSON.stringify({ email, first_name, last_name, date_of_birth, gender, address, preferred_lang, company, job_title, industry, experience, password, re_password });
try {
- const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/`, body, config);
+ const res = await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/users/`, body, config);
dispatch({
type: SIGNUP_SUCCESS,
@@ -208,7 +207,7 @@ export const verify = (uid, token) => async dispatch => {
const body = JSON.stringify({ uid, token });
try {
- await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/activation/`, body, config);
+ await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/users/activation/`, body, config);
dispatch({
type: ACTIVATION_SUCCESS,
@@ -230,7 +229,7 @@ export const reset_password = (email) => async dispatch => {
const body = JSON.stringify({ email });
try {
- await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/reset_password/`, body, config);
+ await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/users/reset_password/`, body, config);
dispatch({
type: PASSWORD_RESET_SUCCESS
@@ -252,7 +251,7 @@ export const reset_password_confirm = (uid, token, new_password, re_new_password
const body = JSON.stringify({ uid, token, new_password, re_new_password });
try {
- await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/reset_password_confirm/`, body, config);
+ await axios.post(`${import.meta.env.VITE_REACT_APP_API_URL}/api/users/reset_password_confirm/`, body, config);
dispatch({
type: PASSWORD_RESET_CONFIRM_SUCCESS
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 9f14cb5..38125b4 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -1,43 +1,39 @@
/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {
- screens :{
- 'max-md' : {'max': '1024px'},
- },
- colors: {
- 'blue': '#0085FF',
- 'red' : '#FF1818',
- 'green' : '#00B612',
- 'yellow' : '#FD9902',
- 'blue_bg' : '#B9DDFF',
- 'red_bg' : '#FFE3E3',
- 'green_bg' : '#C3FFBE',
- 'yellow_bg' : '#FFEDD2',
-
- // Light theme colors
- 'l_grey' : '#868686',
- 'l_light_grey' : '#E3E1E3',
+const plugin = require("tailwindcss/plugin");
- // Dark theme colors
- 'd_black' : '#0E0E0E',
- 'd_grey_prim' : '#202020',
- 'd_white' : '#EAEAEA',
- 'd_light_grey' : '#D3D3D3',
- 'd_grey_border' : '#363636',
- 'd_white_button' : '#EFEFEF',
- 'd_grey_sec' : '#363636',
- 'd_light_grey_ph' : '#9F9F9F'
- },
- fontFamily: {
- sans: ['Poppins', 'sans-serif'],
- }
+module.exports = {
+ mode: 'jit',
+ darkMode: 'selector',
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ screens :{
+ 'max-md' : {'max': '1024px'},
+ },
+ fontFamily: {
+ sans: ['Poppins', 'sans-serif'],
+ }
+ },
},
- },
- plugins: [],
+ plugins: [
+ plugin(({ addVariant, e, postcss }) => {
+ addVariant('firefox', ({ container, separator }) => {
+ const isFirefoxRule = postcss.atRule({
+ name: '-moz-document',
+ params: 'url-prefix()',
+ });
+ isFirefoxRule.append(container.nodes);
+ container.append(isFirefoxRule);
+ isFirefoxRule.walkRules((rule) => {
+ rule.selector = `.${e(
+ `firefox${separator}${rule.selector.slice(1)}`
+ )}`;
+ });
+ });
+ }),
+ ],
}
\ No newline at end of file
diff --git a/frontend/test/Button.test.js b/frontend/test/Button.test.js
new file mode 100644
index 0000000..c785f17
--- /dev/null
+++ b/frontend/test/Button.test.js
@@ -0,0 +1,37 @@
+import { expect, test } from 'vitest';
+import {Button} from '../src/components/Button'
+
+describe('Button components', () => {
+ test('renders button with text', () => {
+ const buttonText = 'Click me';
+ const { getByText } = render( );
+ const button = getByText(buttonText);
+ expect(button).toBeInTheDocument();
+ });
+
+ test('calls onClick prop when clicked', () => {
+ const handleClick = jest.fn();
+ const { getByText } = render( );
+ const button = getByText('Click me');
+ fireEvent.click(button);
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+
+ test('renders previous step icon if isPreviousStep is true', () => {
+ const { container } = render( );
+ const icon = container.querySelector('svg[data-testid="previous-icon"]');
+ expect(icon).toBeInTheDocument();
+ });
+
+ test('renders forward step icon if isForwardStep is true', () => {
+ const { container } = render( );
+ const icon = container.querySelector('svg[data-testid="forward-icon"]');
+ expect(icon).toBeInTheDocument();
+ });
+
+ test('applies custom classname', () => {
+ const { container } = render( );
+ const button = container.querySelector('.custom-class');
+ expect(button).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index cca54b7..3c0f211 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -5,6 +5,9 @@ import svgrPlugin from 'vite-plugin-svgr'
// see all documentation here https://vitejs.dev/config/
export default defineConfig({
+ server: {
+ host: '0.0.0.0'
+ },
// This changes the output dir from dist to build change as your need
// comment this out if that isn't relevant for your project
build: {
diff --git a/qodana.yaml b/qodana.yaml
index 84e3e49..99abbda 100644
--- a/qodana.yaml
+++ b/qodana.yaml
@@ -4,7 +4,7 @@
#-------------------------------------------------------------------------------#
version: "1.0"
-#Specify inspection profile for code analysis
+#Specify inspection Profile for code analysis
profile:
name: qodana.starter