diff --git a/Makefile b/Makefile index 432a409..f4ca743 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install dev build clean +.PHONY: install dev build clean lint install: # Install backend dependencies @@ -25,3 +25,9 @@ clean: rm -rf todoqueue_backend/staticfiles find . -name '*.pyc' -delete find . -name '__pycache__' -delete + +lint: + # Lint Python code with Black + black todoqueue_backend + # Lint JavaScript code with ESLint + cd todoqueue_frontend && npx eslint . diff --git a/todoqueue_backend/accounts/apps.py b/todoqueue_backend/accounts/apps.py index 3e3c765..0cb51e6 100644 --- a/todoqueue_backend/accounts/apps.py +++ b/todoqueue_backend/accounts/apps.py @@ -2,5 +2,5 @@ class AccountsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'accounts' + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" diff --git a/todoqueue_backend/accounts/migrations/0001_initial.py b/todoqueue_backend/accounts/migrations/0001_initial.py index 50eedbc..750954b 100644 --- a/todoqueue_backend/accounts/migrations/0001_initial.py +++ b/todoqueue_backend/accounts/migrations/0001_initial.py @@ -9,29 +9,72 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='CustomUser', + name="CustomUser", 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')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('email', models.EmailField(max_length=254, unique=True)), - ('username', models.CharField(max_length=30, unique=True)), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), - ('is_active', models.BooleanField(default=True)), - ('is_staff', models.BooleanField(default=False)), - ('brownie_point_credit', models.JSONField(default=dict)), - ('brownie_point_debit', models.JSONField(default=dict)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ( + "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" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ("email", models.EmailField(max_length=254, unique=True)), + ("username", models.CharField(max_length=30, unique=True)), + ( + "date_joined", + models.DateTimeField(default=django.utils.timezone.now), + ), + ("is_active", models.BooleanField(default=True)), + ("is_staff", models.BooleanField(default=False)), + ("brownie_point_credit", models.JSONField(default=dict)), + ("brownie_point_debit", models.JSONField(default=dict)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), ] diff --git a/todoqueue_backend/accounts/migrations/0002_alter_customuser_username.py b/todoqueue_backend/accounts/migrations/0002_alter_customuser_username.py index 0a496c6..d7fd02e 100644 --- a/todoqueue_backend/accounts/migrations/0002_alter_customuser_username.py +++ b/todoqueue_backend/accounts/migrations/0002_alter_customuser_username.py @@ -7,13 +7,17 @@ class Migration(migrations.Migration): dependencies = [ - ('accounts', '0001_initial'), + ("accounts", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='customuser', - name='username', - field=models.CharField(max_length=30, unique=True, validators=[accounts.models.validate_profanity]), + model_name="customuser", + name="username", + field=models.CharField( + max_length=30, + unique=True, + validators=[accounts.models.validate_profanity], + ), ), ] diff --git a/todoqueue_backend/accounts/migrations/0003_alter_customuser_username.py b/todoqueue_backend/accounts/migrations/0003_alter_customuser_username.py index d390284..540e75f 100644 --- a/todoqueue_backend/accounts/migrations/0003_alter_customuser_username.py +++ b/todoqueue_backend/accounts/migrations/0003_alter_customuser_username.py @@ -7,13 +7,15 @@ class Migration(migrations.Migration): dependencies = [ - ('accounts', '0002_alter_customuser_username'), + ("accounts", "0002_alter_customuser_username"), ] operations = [ migrations.AlterField( - model_name='customuser', - name='username', - field=models.CharField(max_length=30, validators=[accounts.models.validate_profanity]), + model_name="customuser", + name="username", + field=models.CharField( + max_length=30, validators=[accounts.models.validate_profanity] + ), ), ] diff --git a/todoqueue_backend/accounts/migrations/0004_customuser_has_logged_in.py b/todoqueue_backend/accounts/migrations/0004_customuser_has_logged_in.py index 90fd105..9b6ea34 100644 --- a/todoqueue_backend/accounts/migrations/0004_customuser_has_logged_in.py +++ b/todoqueue_backend/accounts/migrations/0004_customuser_has_logged_in.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('accounts', '0003_alter_customuser_username'), + ("accounts", "0003_alter_customuser_username"), ] operations = [ migrations.AddField( - model_name='customuser', - name='has_logged_in', + model_name="customuser", + name="has_logged_in", field=models.BooleanField(default=False), ), ] diff --git a/todoqueue_backend/accounts/models.py b/todoqueue_backend/accounts/models.py index bae54a1..199281b 100644 --- a/todoqueue_backend/accounts/models.py +++ b/todoqueue_backend/accounts/models.py @@ -42,7 +42,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): date_joined = models.DateTimeField(default=timezone.now) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) - + has_logged_in = models.BooleanField(default=False) brownie_point_credit = models.JSONField(default=dict) diff --git a/todoqueue_backend/accounts/serializers.py b/todoqueue_backend/accounts/serializers.py index a1e1710..db34bb1 100644 --- a/todoqueue_backend/accounts/serializers.py +++ b/todoqueue_backend/accounts/serializers.py @@ -37,10 +37,16 @@ class Meta(CustomUserSerializer.Meta): class CustomUserRegistrationSerializer(serializers.ModelSerializer): email = serializers.EmailField( - validators=[UniqueValidator(queryset=get_user_model().objects.all()), validate_profanity] + validators=[ + UniqueValidator(queryset=get_user_model().objects.all()), + validate_profanity, + ] ) username = serializers.CharField( - validators=[UniqueValidator(queryset=get_user_model().objects.all()), validate_profanity] + validators=[ + UniqueValidator(queryset=get_user_model().objects.all()), + validate_profanity, + ] ) password = serializers.CharField(write_only=True) diff --git a/todoqueue_backend/accounts/tests.py b/todoqueue_backend/accounts/tests.py index d301136..6ab9e9a 100644 --- a/todoqueue_backend/accounts/tests.py +++ b/todoqueue_backend/accounts/tests.py @@ -9,6 +9,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from logging import getLogger + logger = getLogger(__name__) from .serializers import CustomUserSerializer @@ -173,44 +174,46 @@ def test_complete_forgot_password(self): ) def test_register_existing_email(self): - url = reverse('register') + url = reverse("register") data = { "email": "testuser@example.com", "username": "someotheruser", - "password": "somepass" + "password": "somepass", } - response = self.client.post(url, data, format='json') + response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIn('detail', response.data) - self.assertEqual(response.data['detail'], 'User with this email already exists.') + self.assertIn("detail", response.data) + self.assertEqual( + response.data["detail"], "User with this email already exists." + ) def test_failed_login(self): - url = reverse('token_obtain_pair') - data = { - "email": "testuser@example.com", - "password": "wrongpassword" - } - response = self.client.post(url, data, format='json') + url = reverse("token_obtain_pair") + data = {"email": "testuser@example.com", "password": "wrongpassword"} + response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - self.assertEqual(response.data["detail"], "No active account found with the given credentials") + self.assertEqual( + response.data["detail"], + "No active account found with the given credentials", + ) def test_custom_user_viewset_list(self): # First get a token for the user token = RefreshToken.for_user(self.user) - self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.access_token}') - + self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token.access_token}") + # Now, retrieve list of users - url = reverse('customuser-list') - response = self.client.get(url, format='json') + url = reverse("customuser-list") + response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_custom_user_viewset_retrieve(self): # First get a token for the user token = RefreshToken.for_user(self.user) - self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.access_token}') - + self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token.access_token}") + # Now, retrieve the user's own details - url = reverse('customuser-detail', args=[self.user.id]) - response = self.client.get(url, format='json') + url = reverse("customuser-detail", args=[self.user.id]) + response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['email'], self.user.email) + self.assertEqual(response.data["email"], self.user.email) diff --git a/todoqueue_backend/accounts/utils.py b/todoqueue_backend/accounts/utils.py index 2f68ab9..06b1e19 100644 --- a/todoqueue_backend/accounts/utils.py +++ b/todoqueue_backend/accounts/utils.py @@ -3,15 +3,19 @@ from datetime import timedelta from logging import getLogger, DEBUG, basicConfig + basicConfig(level=DEBUG) logger = getLogger(__name__) -def is_rate_limited(identifier, request_type, max_attempts=5, period=timedelta(hours=1)): + +def is_rate_limited( + identifier, request_type, max_attempts=5, period=timedelta(hours=1) +): """ Check if the identifier has exceeded the maximum number of attempts within the period. """ logger.debug("Checking rate limit") - + current_time = timezone.now() cache_key = f"password_reset_attempts_{request_type}_{identifier}" attempts = cache.get(cache_key, []) @@ -27,4 +31,4 @@ def is_rate_limited(identifier, request_type, max_attempts=5, period=timedelta(h attempts.append(current_time) cache.set(cache_key, attempts, period.total_seconds()) - return False \ No newline at end of file + return False diff --git a/todoqueue_backend/accounts/views.py b/todoqueue_backend/accounts/views.py index 2318d2f..7d59509 100644 --- a/todoqueue_backend/accounts/views.py +++ b/todoqueue_backend/accounts/views.py @@ -54,7 +54,7 @@ def check_permissions(self, request): class GetUserData(APIView): permission_classes = (IsAuthenticated,) - + def get(self, request): """Return the serialization of the user who authenticated this request""" logger.info("Getting serialization of a single user") @@ -62,9 +62,10 @@ def get(self, request): serializer = CustomUserSerializer(user) serialized_data = serializer.data logger.info(f"User data: {serialized_data}") - + return Response(serialized_data) + class AuthView(APIView): permission_classes = (IsAuthenticated,) @@ -94,7 +95,10 @@ class RegisterView(APIView): def post(self, request): # Check if the rate limit has been exceeded if is_rate_limited( - request.META['REMOTE_ADDR'], "init_registration", max_attempts=20, period=timedelta(hours=1) + request.META["REMOTE_ADDR"], + "init_registration", + max_attempts=20, + period=timedelta(hours=1), ): return Response( {"detail": "Registration requests are limited to 20 per hour."}, @@ -184,7 +188,10 @@ class ConfirmRegistrationView(APIView): def get(self, request, uidb64, token): # Check if the rate limit has been exceeded if is_rate_limited( - request.META['REMOTE_ADDR'], "confirm_registration", max_attempts=50, period=timedelta(hours=1) + request.META["REMOTE_ADDR"], + "confirm_registration", + max_attempts=50, + period=timedelta(hours=1), ): return Response( {"detail": "Please stop spamming the confirmation endpoint."}, @@ -216,7 +223,10 @@ class ForgotPasswordView(APIView): def post(self, request): # Check if the rate limit has been exceeded if is_rate_limited( - request.META['REMOTE_ADDR'], "forgot_password", max_attempts=5, period=timedelta(hours=1) + request.META["REMOTE_ADDR"], + "forgot_password", + max_attempts=5, + period=timedelta(hours=1), ): return Response( {"detail": "Password reset requests are limited to 5 per hour."}, @@ -291,7 +301,10 @@ class CompleteForgotPasswordView(APIView): def post(self, request, uidb64, token): # Check if the rate limit has been exceeded if is_rate_limited( - request.META['REMOTE_ADDR'], "new_password", max_attempts=20, period=timedelta(hours=1) + request.META["REMOTE_ADDR"], + "new_password", + max_attempts=20, + period=timedelta(hours=1), ): return Response( {"detail": "Please stop spamming the password reset endpoint."}, diff --git a/todoqueue_backend/manage.py b/todoqueue_backend/manage.py index a1d9bf6..b61003a 100755 --- a/todoqueue_backend/manage.py +++ b/todoqueue_backend/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todoqueue_backend.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todoqueue_backend.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/todoqueue_backend/tasks/apps.py b/todoqueue_backend/tasks/apps.py index 3ff3ab3..88a2d12 100644 --- a/todoqueue_backend/tasks/apps.py +++ b/todoqueue_backend/tasks/apps.py @@ -2,5 +2,5 @@ class TasksConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'tasks' + default_auto_field = "django.db.models.BigAutoField" + name = "tasks" diff --git a/todoqueue_backend/tasks/migrations/0001_initial.py b/todoqueue_backend/tasks/migrations/0001_initial.py index acc75e4..2cc62ef 100644 --- a/todoqueue_backend/tasks/migrations/0001_initial.py +++ b/todoqueue_backend/tasks/migrations/0001_initial.py @@ -13,58 +13,129 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0002_remove_content_type_name'), + ("contenttypes", "0002_remove_content_type_name"), ] operations = [ migrations.CreateModel( - name='Household', + name="Household", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('users', models.ManyToManyField(related_name='households', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ( + "users", + models.ManyToManyField( + related_name="households", to=settings.AUTH_USER_MODEL + ), + ), ], ), migrations.CreateModel( - name='WorkLog', + name="WorkLog", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('grossness', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(5)])), - ('completion_time', models.DurationField()), - ('brownie_points', models.IntegerField()), - ('object_id', models.UUIDField()), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "grossness", + models.FloatField( + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(5), + ] + ), + ), + ("completion_time", models.DurationField()), + ("brownie_points", models.IntegerField()), + ("object_id", models.UUIDField()), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ScheduledTask', + name="ScheduledTask", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('task_name', models.CharField(max_length=255)), - ('description', models.TextField(default='')), - ('last_completed', models.DateTimeField(auto_now_add=True)), - ('frozen', models.BooleanField(default=False)), - ('recur_dayhour', models.IntegerField(default=-1)), - ('recur_weekday', models.IntegerField(default=-1)), - ('recur_monthday', models.IntegerField(default=-1)), - ('recur_yearmonth', models.IntegerField(default=-1)), - ('max_interval', models.DurationField(default='0:0')), - ('household', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_tasks', to='tasks.household')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("task_name", models.CharField(max_length=255)), + ("description", models.TextField(default="")), + ("last_completed", models.DateTimeField(auto_now_add=True)), + ("frozen", models.BooleanField(default=False)), + ("recur_dayhour", models.IntegerField(default=-1)), + ("recur_weekday", models.IntegerField(default=-1)), + ("recur_monthday", models.IntegerField(default=-1)), + ("recur_yearmonth", models.IntegerField(default=-1)), + ("max_interval", models.DurationField(default="0:0")), + ( + "household", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="scheduled_tasks", + to="tasks.household", + ), + ), ], ), migrations.CreateModel( - name='FlexibleTask', + name="FlexibleTask", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('task_name', models.CharField(max_length=255)), - ('description', models.TextField(default='')), - ('max_interval', models.DurationField(default='0:0')), - ('min_interval', models.DurationField(default='0:0')), - ('last_completed', models.DateTimeField(auto_now_add=True)), - ('frozen', models.BooleanField(default=False)), - ('household', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flexible_tasks', to='tasks.household')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("task_name", models.CharField(max_length=255)), + ("description", models.TextField(default="")), + ("max_interval", models.DurationField(default="0:0")), + ("min_interval", models.DurationField(default="0:0")), + ("last_completed", models.DateTimeField(auto_now_add=True)), + ("frozen", models.BooleanField(default=False)), + ( + "household", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="flexible_tasks", + to="tasks.household", + ), + ), ], ), ] diff --git a/todoqueue_backend/tasks/migrations/0002_remove_scheduledtask_recur_dayhour_and_more.py b/todoqueue_backend/tasks/migrations/0002_remove_scheduledtask_recur_dayhour_and_more.py index eaf289b..6df4d8e 100644 --- a/todoqueue_backend/tasks/migrations/0002_remove_scheduledtask_recur_dayhour_and_more.py +++ b/todoqueue_backend/tasks/migrations/0002_remove_scheduledtask_recur_dayhour_and_more.py @@ -6,29 +6,29 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0001_initial'), + ("tasks", "0001_initial"), ] operations = [ migrations.RemoveField( - model_name='scheduledtask', - name='recur_dayhour', + model_name="scheduledtask", + name="recur_dayhour", ), migrations.RemoveField( - model_name='scheduledtask', - name='recur_monthday', + model_name="scheduledtask", + name="recur_monthday", ), migrations.RemoveField( - model_name='scheduledtask', - name='recur_weekday', + model_name="scheduledtask", + name="recur_weekday", ), migrations.RemoveField( - model_name='scheduledtask', - name='recur_yearmonth', + model_name="scheduledtask", + name="recur_yearmonth", ), migrations.AddField( - model_name='scheduledtask', - name='cron_schedule', - field=models.CharField(default='0 * * * *', max_length=255), + model_name="scheduledtask", + name="cron_schedule", + field=models.CharField(default="0 * * * *", max_length=255), ), ] diff --git a/todoqueue_backend/tasks/migrations/0003_dummytask.py b/todoqueue_backend/tasks/migrations/0003_dummytask.py index 8c4b65f..301d1e3 100644 --- a/todoqueue_backend/tasks/migrations/0003_dummytask.py +++ b/todoqueue_backend/tasks/migrations/0003_dummytask.py @@ -8,17 +8,32 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0002_remove_scheduledtask_recur_dayhour_and_more'), + ("tasks", "0002_remove_scheduledtask_recur_dayhour_and_more"), ] operations = [ migrations.CreateModel( - name='DummyTask', + name="DummyTask", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('last_completed', models.DateTimeField(auto_now_add=True)), - ('frozen', models.BooleanField(default=False)), - ('household', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dummy_tasks', to='tasks.household')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("last_completed", models.DateTimeField(auto_now_add=True)), + ("frozen", models.BooleanField(default=False)), + ( + "household", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="dummy_tasks", + to="tasks.household", + ), + ), ], ), ] diff --git a/todoqueue_backend/tasks/migrations/0004_alter_flexibletask_description_and_more.py b/todoqueue_backend/tasks/migrations/0004_alter_flexibletask_description_and_more.py index c01dfce..6e93932 100644 --- a/todoqueue_backend/tasks/migrations/0004_alter_flexibletask_description_and_more.py +++ b/todoqueue_backend/tasks/migrations/0004_alter_flexibletask_description_and_more.py @@ -7,33 +7,43 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0003_dummytask'), + ("tasks", "0003_dummytask"), ] operations = [ migrations.AlterField( - model_name='flexibletask', - name='description', - field=models.TextField(default='', validators=[tasks.models.validate_profanity]), + model_name="flexibletask", + name="description", + field=models.TextField( + default="", validators=[tasks.models.validate_profanity] + ), ), migrations.AlterField( - model_name='flexibletask', - name='task_name', - field=models.CharField(max_length=255, validators=[tasks.models.validate_profanity]), + model_name="flexibletask", + name="task_name", + field=models.CharField( + max_length=255, validators=[tasks.models.validate_profanity] + ), ), migrations.AlterField( - model_name='household', - name='name', - field=models.CharField(max_length=255, validators=[tasks.models.validate_profanity]), + model_name="household", + name="name", + field=models.CharField( + max_length=255, validators=[tasks.models.validate_profanity] + ), ), migrations.AlterField( - model_name='scheduledtask', - name='description', - field=models.TextField(default='', validators=[tasks.models.validate_profanity]), + model_name="scheduledtask", + name="description", + field=models.TextField( + default="", validators=[tasks.models.validate_profanity] + ), ), migrations.AlterField( - model_name='scheduledtask', - name='task_name', - field=models.CharField(max_length=255, validators=[tasks.models.validate_profanity]), + model_name="scheduledtask", + name="task_name", + field=models.CharField( + max_length=255, validators=[tasks.models.validate_profanity] + ), ), ] diff --git a/todoqueue_backend/tasks/migrations/0005_invitation.py b/todoqueue_backend/tasks/migrations/0005_invitation.py index 577a886..46a8280 100644 --- a/todoqueue_backend/tasks/migrations/0005_invitation.py +++ b/todoqueue_backend/tasks/migrations/0005_invitation.py @@ -9,19 +9,47 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('tasks', '0004_alter_flexibletask_description_and_more'), + ("tasks", "0004_alter_flexibletask_description_and_more"), ] operations = [ migrations.CreateModel( - name='Invitation', + name="Invitation", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('accepted', models.BooleanField(default=False)), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('household', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.household')), - ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_invitations', to=settings.AUTH_USER_MODEL)), - ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_invitations', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("accepted", models.BooleanField(default=False)), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "household", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="tasks.household", + ), + ), + ( + "recipient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="received_invitations", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "sender", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sent_invitations", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/todoqueue_backend/tasks/migrations/0006_oneshottask.py b/todoqueue_backend/tasks/migrations/0006_oneshottask.py index 0ae34d5..6202922 100644 --- a/todoqueue_backend/tasks/migrations/0006_oneshottask.py +++ b/todoqueue_backend/tasks/migrations/0006_oneshottask.py @@ -9,22 +9,47 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0005_invitation'), + ("tasks", "0005_invitation"), ] operations = [ migrations.CreateModel( - name='OneShotTask', + name="OneShotTask", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('task_name', models.CharField(max_length=255, validators=[tasks.models.validate_profanity])), - ('description', models.TextField(default='', validators=[tasks.models.validate_profanity])), - ('due_date', models.DateField()), - ('due_before', models.BooleanField(default=False)), - ('time_to_complete', models.DurationField(default='0:0')), - ('frozen', models.BooleanField(default=False)), - ('has_completed', models.BooleanField(default=False)), - ('household', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='oneshot_tasks', to='tasks.household')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "task_name", + models.CharField( + max_length=255, validators=[tasks.models.validate_profanity] + ), + ), + ( + "description", + models.TextField( + default="", validators=[tasks.models.validate_profanity] + ), + ), + ("due_date", models.DateField()), + ("due_before", models.BooleanField(default=False)), + ("time_to_complete", models.DurationField(default="0:0")), + ("frozen", models.BooleanField(default=False)), + ("has_completed", models.BooleanField(default=False)), + ( + "household", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="oneshot_tasks", + to="tasks.household", + ), + ), ], ), ] diff --git a/todoqueue_backend/tasks/migrations/0007_alter_oneshottask_due_date.py b/todoqueue_backend/tasks/migrations/0007_alter_oneshottask_due_date.py index b7a28a4..1db38c6 100644 --- a/todoqueue_backend/tasks/migrations/0007_alter_oneshottask_due_date.py +++ b/todoqueue_backend/tasks/migrations/0007_alter_oneshottask_due_date.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0006_oneshottask'), + ("tasks", "0006_oneshottask"), ] operations = [ migrations.AlterField( - model_name='oneshottask', - name='due_date', + model_name="oneshottask", + name="due_date", field=models.DateTimeField(), ), ] diff --git a/todoqueue_backend/tasks/migrations/0008_oneshottask_last_completed.py b/todoqueue_backend/tasks/migrations/0008_oneshottask_last_completed.py index d154c74..2d250f7 100644 --- a/todoqueue_backend/tasks/migrations/0008_oneshottask_last_completed.py +++ b/todoqueue_backend/tasks/migrations/0008_oneshottask_last_completed.py @@ -7,14 +7,19 @@ class Migration(migrations.Migration): dependencies = [ - ('tasks', '0007_alter_oneshottask_due_date'), + ("tasks", "0007_alter_oneshottask_due_date"), ] operations = [ migrations.AddField( - model_name='oneshottask', - name='last_completed', - field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2023, 11, 6, 20, 50, 34, 981370, tzinfo=datetime.timezone.utc)), + model_name="oneshottask", + name="last_completed", + field=models.DateTimeField( + auto_now_add=True, + default=datetime.datetime( + 2023, 11, 6, 20, 50, 34, 981370, tzinfo=datetime.timezone.utc + ), + ), preserve_default=False, ), ] diff --git a/todoqueue_backend/tasks/models.py b/todoqueue_backend/tasks/models.py index b563165..bf7885e 100644 --- a/todoqueue_backend/tasks/models.py +++ b/todoqueue_backend/tasks/models.py @@ -239,6 +239,7 @@ def __str__(self): class OneShotTask(models.Model): """One-shot tasks only appear once, then when they are completed they never stop being fresh again""" + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) task_name = models.CharField(max_length=255, validators=[validate_profanity]) diff --git a/todoqueue_backend/tasks/plot_brownie_points.py b/todoqueue_backend/tasks/plot_brownie_points.py index 423d311..7f77ec3 100644 --- a/todoqueue_backend/tasks/plot_brownie_points.py +++ b/todoqueue_backend/tasks/plot_brownie_points.py @@ -19,14 +19,14 @@ # Create a surface plot fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.plot_surface(A, B, Z, cmap='viridis') +ax = fig.add_subplot(111, projection="3d") +ax.plot_surface(A, B, Z, cmap="viridis") # Set labels and title -ax.set_xlabel('Time') -ax.set_ylabel('Grossness') -ax.set_zlabel('BP Score') +ax.set_xlabel("Time") +ax.set_ylabel("Grossness") +ax.set_zlabel("BP Score") ax.set_zlim(0, 300) diff --git a/todoqueue_backend/tasks/urls.py b/todoqueue_backend/tasks/urls.py index f9d505d..dbe8107 100644 --- a/todoqueue_backend/tasks/urls.py +++ b/todoqueue_backend/tasks/urls.py @@ -13,7 +13,7 @@ urlpatterns = [ path("", include(router.urls)), path("toggle_frozen//", views.toggle_frozen, name="toggle_frozen"), - path('dismiss_task//', views.dismiss_task, name='dismiss_task'), + path("dismiss_task//", views.dismiss_task, name="dismiss_task"), path( "calculate_brownie_points/", views.calculate_brownie_points_view, diff --git a/todoqueue_backend/tasks/utils.py b/todoqueue_backend/tasks/utils.py index 9fe2a06..b8f1116 100644 --- a/todoqueue_backend/tasks/utils.py +++ b/todoqueue_backend/tasks/utils.py @@ -77,11 +77,11 @@ def bp_function( Returns: float: The brownie points """ - + if completion_time_minutes == 0: logger.debug("Completion time is 0, returning 0 brownie points") return 0 - + user_gross_scale_range = [0, 5] output_gross_scale_range = [0, 100] @@ -92,14 +92,14 @@ def bp_function( # completion_time_minutes = piecewise_linear(completion_time_minutes, 2.0, 0.75, 30) - random_factor = 1 #random.uniform(1.0, 1.1) + random_factor = 1 # random.uniform(1.0, 1.1) random_base = random.uniform(0, 50) # Calculate the brownie points # The sigmoid scales are just hand tuned to make the graph look nice brownie_points = 200 * sigmoid(completion_time_minutes / 20) + grossness - 100 brownie_points = (brownie_points * random_factor) + random_base - + logger.debug(f" Completion time: {completion_time_minutes}") logger.debug(f" Grossness: {grossness}") logger.debug(f" Random factor: {random_factor:.2f}") @@ -112,4 +112,3 @@ def bp_function( def validate_profanity(value): logger.info("Validating profanity") return is_profane([value])[0] == 1 - diff --git a/todoqueue_backend/todoqueue_backend/asgi.py b/todoqueue_backend/todoqueue_backend/asgi.py index 176a0da..949c896 100644 --- a/todoqueue_backend/todoqueue_backend/asgi.py +++ b/todoqueue_backend/todoqueue_backend/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todoqueue_backend.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todoqueue_backend.settings") application = get_asgi_application() diff --git a/todoqueue_backend/todoqueue_backend/settings.py b/todoqueue_backend/todoqueue_backend/settings.py index 2555e5c..ecacfb4 100644 --- a/todoqueue_backend/todoqueue_backend/settings.py +++ b/todoqueue_backend/todoqueue_backend/settings.py @@ -38,7 +38,7 @@ def get_env_variable(var_name, default=None, cast_type=str): # Load environment variables from .env file if get_env_variable("DEV", False, bool): - dotenv_path = path.join(path.dirname(__file__), "..", '.env') + dotenv_path = path.join(path.dirname(__file__), "..", ".env") print(f"Checking for env: {dotenv_path}") if path.exists(dotenv_path): print("Loading env") diff --git a/todoqueue_backend/todoqueue_backend/wsgi.py b/todoqueue_backend/todoqueue_backend/wsgi.py index 244e950..3c23707 100644 --- a/todoqueue_backend/todoqueue_backend/wsgi.py +++ b/todoqueue_backend/todoqueue_backend/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todoqueue_backend.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todoqueue_backend.settings") application = get_wsgi_application() diff --git a/todoqueue_frontend/.eslintignore b/todoqueue_frontend/.eslintignore new file mode 100644 index 0000000..324ca6b --- /dev/null +++ b/todoqueue_frontend/.eslintignore @@ -0,0 +1,5 @@ +# node_modules/* and /bower_components/* in the project root are ignored by default + +# Ignore built files except build/index.js +build/* +!build/index.js diff --git a/todoqueue_frontend/.eslintrc.json b/todoqueue_frontend/.eslintrc.json new file mode 100644 index 0000000..d4a716d --- /dev/null +++ b/todoqueue_frontend/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "react-app", + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "react", + "@typescript-eslint" + ], + "rules": { + "react/prop-types": "off" + } +} diff --git a/todoqueue_frontend/src/App.js b/todoqueue_frontend/src/App.js index bcd2b15..2fb1bb3 100644 --- a/todoqueue_frontend/src/App.js +++ b/todoqueue_frontend/src/App.js @@ -2,7 +2,7 @@ import './App.css'; import 'bootstrap/dist/css/bootstrap.min.css'; import React, { useState, useEffect } from 'react'; -import { BrowserRouter as Router, Route, Routes, BrowserRouter } from 'react-router-dom'; +import { Route, Routes, BrowserRouter } from 'react-router-dom'; import { Helmet } from 'react-helmet'; @@ -57,6 +57,7 @@ const App = () => { const interval = setInterval(updateHouseholds, 1000); return () => clearInterval(interval); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedHousehold]); @@ -65,7 +66,7 @@ const App = () => { - + { render(); diff --git a/todoqueue_frontend/src/api/backend_url.js b/todoqueue_frontend/src/api/backend_url.js index d9a0e7f..e92d7df 100644 --- a/todoqueue_frontend/src/api/backend_url.js +++ b/todoqueue_frontend/src/api/backend_url.js @@ -1,4 +1,2 @@ -const development = "http://localhost:8000"; -const production = ""; - -export const backend_url = production; \ No newline at end of file +export const backend_url = "http://localhost:8000"; +// export const backend_url = ""; // For production diff --git a/todoqueue_frontend/src/api/users.js b/todoqueue_frontend/src/api/users.js index 6e85b51..b856267 100644 --- a/todoqueue_frontend/src/api/users.js +++ b/todoqueue_frontend/src/api/users.js @@ -37,7 +37,7 @@ export const loginUser = async (email, password) => { return { error: "Error logging in, please try again or reset your password." }; } console.log("Login successful, setting access token.", data); - // Initialize the access & refresh token in localstorage. + // Initialize the access & refresh token in localstorage. localStorage.clear(); localStorage.setItem('access_token', data.access); localStorage.setItem('refresh_token', data.refresh); @@ -54,27 +54,27 @@ export const loginUser = async (email, password) => { export const logOutUser = async () => { console.log("Logging out user"); try { - await axios.post( - `${backend_url}/api/logout/`, - { - refresh_token: localStorage.getItem('refresh_token') - }, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true - } - ); + await axios.post( + `${backend_url}/api/logout/`, + { + refresh_token: localStorage.getItem('refresh_token') + }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true + } + ); - console.log("Clearing local storage"); - localStorage.clear(); - axios.defaults.headers.common['Authorization'] = null; + console.log("Clearing local storage"); + localStorage.clear(); + axios.defaults.headers.common['Authorization'] = null; - console.log("Redirecting to login page"); - window.location.href = '/login'; + console.log("Redirecting to login page"); + window.location.href = '/login'; } catch (e) { - console.log('logout not working', e); + console.log('logout not working', e); } - } +} export const fetchHouseholdUsers = async (selectedHousehold) => { @@ -175,21 +175,17 @@ export const resetPassword = async (uid, token, newPassword, confirmPassword) => confirm_new_password: confirmPassword, }; - try { - const res = await axios.post( - `${backend_url}/api/complete_forgot_password/${uid}/${token}/`, - payload, - { - headers: { - 'Content-Type': 'application/json' - }, - withCredentials: true + const res = await axios.post( + `${backend_url}/api/complete_forgot_password/${uid}/${token}/`, + payload, + { + headers: { + 'Content-Type': 'application/json' }, - ); - return res.data; - } catch (error) { - throw error; - } + withCredentials: true + }, + ); + return res.data; }; @@ -218,15 +214,15 @@ export const signUp = async (email, username, password) => { ); if (res.status === 201) { console.log("New user created:", res.data); - return { "success": "New user created. Please check your email for a verification link."}; + return { "success": "New user created. Please check your email for a verification link." }; } else { console.log("The registration response contains an error:", res); - return {"error": res.data}; + return { "error": res.data }; } } catch (error) { console.log("Error during registration:", error); - return {"error": "Error during registration."}; + return { "error": "Error during registration." }; } -}; \ No newline at end of file +}; diff --git a/todoqueue_frontend/src/components/FailedRegistration.js b/todoqueue_frontend/src/components/FailedRegistration.js index bc62eda..92d5629 100644 --- a/todoqueue_frontend/src/components/FailedRegistration.js +++ b/todoqueue_frontend/src/components/FailedRegistration.js @@ -8,9 +8,9 @@ const FailedRegistration = () => { return (

Account activation failed!

-

Please retry account creation, or double-check you've used the correct email.

+

Please retry account creation, or double-check you've used the correct email.

); } -export default FailedRegistration; \ No newline at end of file +export default FailedRegistration; diff --git a/todoqueue_frontend/src/components/about.js b/todoqueue_frontend/src/components/about.js index e597b90..5bdfc74 100644 --- a/todoqueue_frontend/src/components/about.js +++ b/todoqueue_frontend/src/components/about.js @@ -1,4 +1,4 @@ -import { React } from 'react'; +import { React } from "react"; import "./simple_page.css"; @@ -6,25 +6,25 @@ export const About = () => { return (

- Juggling infrequent tasks is easier when they're broken down and spread out. Instead of losing a whole day to chores, ToDoQu lets you tackle them in brief, manageable moments. This way, your Saturdays are saved, and your home stays orderly. + Juggling infrequent tasks is easier when they're broken down and spread out. Instead of losing a whole day to chores, ToDoQu lets you tackle them in brief, manageable moments. This way, your Saturdays are saved, and your home stays orderly.

How It Works

- Take cleaning a living room: it's not one task, but many. With ToDoQu, you can schedule each part—like vacuuming or dusting skirting boards—on its own timeline. Get reminders for vacuuming every 5 to 9 days, or nudge yourself to declutter the coffee table daily. ToDoQu allows you to set the frequency each task needs, and complete them on your own time. + Take cleaning a living room: it's not one task, but many. With ToDoQu, you can schedule each part—like vacuuming or dusting skirting boards—on its own timeline. Get reminders for vacuuming every 5 to 9 days, or nudge yourself to declutter the coffee table daily. ToDoQu allows you to set the frequency each task needs, and complete them on your own time.

- The key is to keep tasks as small as possible, while keeping them distinct but not fragmented. "Do the laundry" is a single, clear task, whereas "Put the laundry in" and "Take the laundry out" are unnecessarily split. + The key is to keep tasks as small as possible, while keeping them distinct but not fragmented. "Do the laundry" is a single, clear task, whereas "Put the laundry in" and "Take the laundry out" are unnecessarily split.

Tracking Effort

- ToDoQu also keeps score, awarding brownie points for task completion based on time spent, difficulty, and a touch of randomness. Tasks can be shared or solo, and if a task stops needing to be done without anyone's intervention, they can be dismissed without crediting anyone at all. + ToDoQu also keeps score, awarding brownie points for task completion based on time spent, difficulty, and a touch of randomness. Tasks can be shared or solo, and if a task stops needing to be done without anyone's intervention, they can be dismissed without crediting anyone at all.

); -} \ No newline at end of file +} diff --git a/todoqueue_frontend/src/components/flipper/flipper.js b/todoqueue_frontend/src/components/flipper/flipper.js index 379a8d3..e72351a 100644 --- a/todoqueue_frontend/src/components/flipper/flipper.js +++ b/todoqueue_frontend/src/components/flipper/flipper.js @@ -34,9 +34,10 @@ const CountdownTracker = ({ value }) => { setPreviousValue(value); el.removeEventListener('animationend', handleAnimationEnd); }; - + el.addEventListener('animationend', handleAnimationEnd); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); return ( diff --git a/todoqueue_frontend/src/components/forgotPassword.js b/todoqueue_frontend/src/components/forgotPassword.js index 81b7f5c..863346d 100644 --- a/todoqueue_frontend/src/components/forgotPassword.js +++ b/todoqueue_frontend/src/components/forgotPassword.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { React, useState, useEffect } from "react"; import { forgotPassword } from "../api/users"; import AlertMessage from "./popups/AlertPopup"; @@ -11,6 +11,7 @@ const ForgotPassword = ({ setShowHouseholdSelector }) => { useEffect(() => { setShowHouseholdSelector(false); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const submit = async e => { @@ -74,4 +75,4 @@ const ForgotPassword = ({ setShowHouseholdSelector }) => { } -export default ForgotPassword; \ No newline at end of file +export default ForgotPassword; diff --git a/todoqueue_frontend/src/components/help.js b/todoqueue_frontend/src/components/help.js index 21fc6c9..dd3c77f 100644 --- a/todoqueue_frontend/src/components/help.js +++ b/todoqueue_frontend/src/components/help.js @@ -1,4 +1,4 @@ -import { React } from 'react'; +import { React } from "react"; import "./simple_page.css"; @@ -6,25 +6,25 @@ export const Help = () => { return (

- Juggling infrequent tasks is easier when they're broken down and spread out. Instead of losing a whole day to chores, ToDoQu lets you tackle them in brief, manageable moments. This way, your Saturdays are saved, and your home stays orderly. + Juggling infrequent tasks is easier when they're broken down and spread out. Instead of losing a whole day to chores, ToDoQu lets you tackle them in brief, manageable moments. This way, your Saturdays are saved, and your home stays orderly.

How It Works

- Take cleaning a living room: it's not one task, but many. With ToDoQu, you can schedule each part—like vacuuming or dusting skirting boards—on its own timeline. Get reminders for vacuuming every 5 to 9 days, or nudge yourself to declutter the coffee table daily. ToDoQu allows you to set the frequency each task needs, and complete them on your own time. + Take cleaning a living room: it's not one task, but many. With ToDoQu, you can schedule each part—like vacuuming or dusting skirting boards—on its own timeline. Get reminders for vacuuming every 5 to 9 days, or nudge yourself to declutter the coffee table daily. ToDoQu allows you to set the frequency each task needs, and complete them on your own time.

- The key is to keep tasks as small as possible, while keeping them distinct but not fragmented. "Do the laundry" is a single, clear task, whereas "Put the laundry in" and "Take the laundry out" are unnecessarily split. + The key is to keep tasks as small as possible, while keeping them distinct but not fragmented. "Do the laundry" is a single, clear task, whereas "Put the laundry in" and "Take the laundry out" are unnecessarily split.

Tracking Effort

- ToDoQu also keeps score, awarding brownie points for task completion based on time spent, difficulty, and a touch of randomness. Tasks can be shared or solo, and if a task stops needing to be done without anyone's intervention, they can be dismissed without crediting anyone at all. + ToDoQu also keeps score, awarding brownie points for task completion based on time spent, difficulty, and a touch of randomness. Tasks can be shared or solo, and if a task stops needing to be done without anyone's intervention, they can be dismissed without crediting anyone at all.

); -} \ No newline at end of file +} diff --git a/todoqueue_frontend/src/components/households/households.js b/todoqueue_frontend/src/components/households/households.js index fa0b6da..a37211f 100644 --- a/todoqueue_frontend/src/components/households/households.js +++ b/todoqueue_frontend/src/components/households/households.js @@ -1,9 +1,9 @@ -import { useEffect, useState, useRef } from "react"; +import { React, useEffect, useState, useRef } from "react"; import useAuthCheck from '../../hooks/authCheck'; import AlertMessage from "../popups/AlertPopup"; -import { createHousehold, deleteHousehold, fetchPendingInvitations, acceptInvitation, declineInvitation } from '../../api/households'; +import { createHousehold, fetchPendingInvitations, acceptInvitation, declineInvitation } from '../../api/households'; import HouseholdDetailsPopup from "../popups/HouseholdDetailsPopup"; import './households.css'; @@ -38,6 +38,7 @@ export const ManageHouseholds = ({ households, updateHouseholds, setShowHousehol // Clear the interval when the component unmounts return () => clearInterval(invitationCheckInterval); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -72,23 +73,23 @@ export const ManageHouseholds = ({ households, updateHouseholds, setShowHousehol }; - const handleDelete = async (id) => { - console.log("Deleting household:", id); - const promise = deleteHousehold(id); + // const handleDelete = async (id) => { + // console.log("Deleting household:", id); + // const promise = deleteHousehold(id); - setErrorMessage(""); - const data = await promise; + // setErrorMessage(""); + // const data = await promise; - if (data.error) { - setErrorMessage(data.error); - } else if (data.success) { - console.log("Successfully deleted household"); - } + // if (data.error) { + // setErrorMessage(data.error); + // } else if (data.success) { + // console.log("Successfully deleted household"); + // } - console.log("Updating household list"); - updateHouseholds(); - }; + // console.log("Updating household list"); + // updateHouseholds(); + // }; const handleAcceptInvitation = async (invitationId) => { diff --git a/todoqueue_frontend/src/components/login/Login.js b/todoqueue_frontend/src/components/login/Login.js index bf03baf..b8b8082 100644 --- a/todoqueue_frontend/src/components/login/Login.js +++ b/todoqueue_frontend/src/components/login/Login.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; // Define the Login function. +import { React, useState, useEffect } from "react"; import "./Login.css"; import "../../utils/buttons.css"; @@ -16,6 +16,7 @@ const Login = ({ setShowHouseholdSelector }) => { useEffect(() => { setShowHouseholdSelector(false); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Create the submit method. @@ -115,4 +116,4 @@ const Login = ({ setShowHouseholdSelector }) => { ) } -export default Login; \ No newline at end of file +export default Login; diff --git a/todoqueue_frontend/src/components/logout.js b/todoqueue_frontend/src/components/logout.js index 1be0e9d..5ae1911 100644 --- a/todoqueue_frontend/src/components/logout.js +++ b/todoqueue_frontend/src/components/logout.js @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { React, useEffect } from "react"; import useAuthCheck from '../hooks/authCheck'; import { logOutUser } from '../api/users'; @@ -10,6 +10,7 @@ export const Logout = ({ setShowHouseholdSelector }) => { useEffect(() => { setShowHouseholdSelector(false); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/todoqueue_frontend/src/components/navbar/navigation.js b/todoqueue_frontend/src/components/navbar/navigation.js index 430c241..ced311e 100644 --- a/todoqueue_frontend/src/components/navbar/navigation.js +++ b/todoqueue_frontend/src/components/navbar/navigation.js @@ -72,7 +72,7 @@ export function Navigation({ households, selectedHousehold, setSelectedHousehold onClick={toggleFullScreen} className="fullscreen-icon" > - + diff --git a/todoqueue_frontend/src/components/popups/AwardBrowniePoints.js b/todoqueue_frontend/src/components/popups/AwardBrowniePoints.js index a1b2a13..6fb8d88 100644 --- a/todoqueue_frontend/src/components/popups/AwardBrowniePoints.js +++ b/todoqueue_frontend/src/components/popups/AwardBrowniePoints.js @@ -64,6 +64,7 @@ const AwardBrowniePointsPopup = React.forwardRef((props, ref) => { .sort((a, b) => a.username.localeCompare(b.username)) .map(user => ( - + diff --git a/todoqueue_frontend/src/components/tasks/Tasks.js b/todoqueue_frontend/src/components/tasks/Tasks.js index 4e82714..61b6af2 100644 --- a/todoqueue_frontend/src/components/tasks/Tasks.js +++ b/todoqueue_frontend/src/components/tasks/Tasks.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import { React, useState, useEffect, useRef } from 'react'; import useAuthCheck from '../../hooks/authCheck'; import './Tasks.css'; @@ -87,6 +87,7 @@ const Tasks = ({ households, selectedHousehold, setSelectedHousehold, showSelect // Show the household selector on first render useEffect(() => { setShowHouseholdSelector(true); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [showSelectedHouseholdSelector]); @@ -104,6 +105,7 @@ const Tasks = ({ households, selectedHousehold, setSelectedHousehold, showSelect fetchSetUsers(); }, 1000); return () => clearInterval(interval); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [showSidebar, selectedHousehold]); @@ -347,7 +349,7 @@ const Tasks = ({ households, selectedHousehold, setSelectedHousehold, showSelect
{tasks .filter(task => task.staleness !== 0) - .map((task, index) => ( + .map((task) => (
-

You've completed all your tasks!

+

You've completed all your tasks!

@@ -400,7 +402,7 @@ const Tasks = ({ households, selectedHousehold, setSelectedHousehold, showSelect // If 'frozen' is equal, sort by 'task_name' in ascending order return a.task_name.localeCompare(b.task_name); }) - .map((task, index) => ( + .map((task) => (
- {sortedUsers.slice(0, 5).map((user, index) => ( + {sortedUsers.slice(0, 5).map((user) => (
{user.username} { } // Extract hours, minutes, and seconds - const [hours, minutes, seconds] = time.split(":").map(Number); + const [hours, minutes] = time.split(":").map(Number); // Construct human-readable string const daysStr = days === "1" ? "1 day" : `${days} days`;