diff --git a/backend/.env.docker b/backend/.env.docker index 5f01a4967ee84ac8b8e19bfbf02e74b07026328c..d8c9981e76edef0fced98c9bb101d33a601ca0a4 100644 --- a/backend/.env.docker +++ b/backend/.env.docker @@ -2,7 +2,7 @@ INFLUXDB_URL=http://influxdb2:8086 INFLUXDB_ORG=docs INFLUXDB_BUCKET=co2-test -INFLUXDB_TOKEN=1xa5lLACRZDYsvinhABndZ8GGzBY7-gTQsAf309c0aTnPPtBxixPEEOPuXLmkTxUKy8golKae6fsrh1wD4SL0A== +INFLUXDB_TOKEN=xHpy7Vdt2_3hNb9DOpc1DHO6UkH19R58_okbCORz5vhDvYaiYJN2e0tl2H4rQlym6w5dCbRweeMWL00DU2ymNw== # MQTT config MQTT_BROKER_URL=mosquitto-broker MQTT_TOPIC="co2/#" \ No newline at end of file diff --git a/backend/app/views.py b/backend/app/views.py index 299efdca4fef758e95571bb99c0067670425d385..626d5e4d8dfe770b1762af5fd91f2e2eef576045 100644 --- a/backend/app/views.py +++ b/backend/app/views.py @@ -33,7 +33,7 @@ def login_view(request): return JsonResponse( {"success": False, "message": "Invalid JSON"}, status=400 ) - + user = authenticate(request, username=email, password=password) if user: @@ -71,6 +71,7 @@ def register(request): errors = form.errors.as_json() return JsonResponse({"error": errors}, status=400) + @require_http_methods(["GET"]) def room_data_range(request): try: @@ -79,8 +80,10 @@ def room_data_range(request): stop = request.GET.get("stop", "now()") if not room: - return JsonResponse({"error": "Missing 'room' parameter"}, status=400) - + return JsonResponse( + {"error": "Missing 'room' parameter"}, status=400 + ) + load_dotenv() client = InfluxDBHelper( url=os.getenv("INFLUXDB_URL"), @@ -93,26 +96,28 @@ def room_data_range(request): results = [] for table in tables: for record in table.records: - results.append({ - "time": str(record.get_time()), - "field": record.get_field(), - "value": record.get_value(), - }) - - return JsonResponse({"room": room, "data": results}, status = 200) + results.append( + { + "time": str(record.get_time()), + "field": record.get_field(), + "value": record.get_value(), + } + ) + + return JsonResponse({"room": room, "data": results}, status=200) except json.JSONDecodeError: return JsonResponse( {"success": False, "message": "Invalid JSON"}, status=400 ) - + + @require_http_methods(["GET"]) def get_rooms(request): - client = InfluxDBHelper( - url=os.getenv("INFLUXDB_URL"), - token=os.getenv("INFLUXDB_TOKEN"), - org=os.getenv("INFLUXDB_ORG"), - bucket=os.getenv("INFLUXDB_BUCKET"), + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), ) tables = client.list_rooms() @@ -123,6 +128,7 @@ def get_rooms(request): return JsonResponse({"rooms": sorted(rooms)}) + @require_http_methods(["GET"]) def room_data_csv_view(request): room = request.GET.get("room") @@ -131,12 +137,12 @@ def room_data_csv_view(request): if not room: return JsonResponse({"error": "Missing 'room' parameter"}, status=400) - + client = InfluxDBHelper( - url=os.getenv("INFLUXDB_URL"), - token=os.getenv("INFLUXDB_TOKEN"), - org=os.getenv("INFLUXDB_ORG"), - bucket=os.getenv("INFLUXDB_BUCKET"), + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), ) csv_stream = client.get_room_data_csv(room_id=room, start=start, stop=stop) @@ -148,33 +154,35 @@ def room_data_csv_view(request): for row in reader: # optional: nur Datenzeilen filtern if row.get("_field") and row.get("_value"): - results.append({ - "time": row["_time"], - "field": row["_field"], - "value": row["_value"], - }) + results.append( + { + "time": row["_time"], + "field": row["_field"], + "value": row["_value"], + } + ) return JsonResponse({"room": room, "data": results}) + @require_http_methods(["GET"]) def room_data_csv_download(request): room = request.GET.get("room") start = request.GET.get("start", "-30d") stop = request.GET.get("stop", "now()") - + if not room: return JsonResponse({"error": "Missing 'room' parameter"}, status=400) - + client = InfluxDBHelper( - url=os.getenv("INFLUXDB_URL"), - token=os.getenv("INFLUXDB_TOKEN"), - org=os.getenv("INFLUXDB_ORG"), - bucket=os.getenv("INFLUXDB_BUCKET"), + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), ) - + csv_data = client.get_room_data_csv(room, start, stop) response = HttpResponse(csv_data, content_type="text/csv") response["Content-Disposition"] = f'attachment; filename="{room}_data.csv"' return response - diff --git a/backend/core/settings.py b/backend/core/settings.py index f9f57409a421487c2f4c1e31783ec5e88903315d..5b35127ad5d1cb222efbaba4fb2308f2bb47c7ca 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -43,9 +43,22 @@ INSTALLED_APPS = [ "corsheaders", ] + CORS_ALLOW_CREDENTIALS = True -CORS_ALLOWED_ORIGINS = ["http://localhost:5173"] # TODO frontend URL here -CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"] # TODO frontend URL here + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:5173", + "http://127.0.0.1:5173", +] # TODO frontend URL here + +CSRF_TRUSTED_ORIGINS = [ + "http://localhost:5173", + "http://127.0.0.1:5173", +] + +CSRF_COOKIE_HTTPONLY = False # allow JS to read the cookie +CSRF_COOKIE_SAMESITE = "Lax" # or 'None' if using https in development +CSRF_COOKIE_SECURE = False # True in production with HTTPS MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", diff --git a/backend/uv.lock b/backend/uv.lock index e9aa3ae9dd29eaa3106659a609e77d96abe5ea62..75c22ab86679daeb33a6e0790a89c88dc93157a4 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -1,14 +1,14 @@ version = 1 -revision = 2 +revision = 1 requires-python = ">=3.12" [[package]] name = "asgiref" version = "3.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, + { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, ] [[package]] @@ -19,7 +19,6 @@ dependencies = [ { name = "django" }, { name = "django-cors-headers" }, { name = "influxdb-client" }, - { name = "paho-mqtt" }, { name = "python-dotenv" }, ] @@ -33,7 +32,6 @@ requires-dist = [ { name = "django", specifier = ">=5.2" }, { name = "django-cors-headers", specifier = ">=4.7.0" }, { name = "influxdb-client", specifier = ">=1.40" }, - { name = "paho-mqtt", specifier = ">=2.1" }, { name = "python-dotenv", specifier = ">=1.1" }, ] @@ -44,9 +42,9 @@ dev = [{ name = "ruff", specifier = ">=0.11.5" }] name = "certifi" version = "2025.4.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, ] [[package]] @@ -58,9 +56,9 @@ dependencies = [ { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/10/0d546258772b8f31398e67c85e52c66ebc2b13a647193c3eef8ee433f1a8/django-5.2.1.tar.gz", hash = "sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284", size = 10818735, upload-time = "2025-05-07T14:06:17.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/10/0d546258772b8f31398e67c85e52c66ebc2b13a647193c3eef8ee433f1a8/django-5.2.1.tar.gz", hash = "sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284", size = 10818735 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/92/7448697b5838b3a1c6e1d2d6a673e908d0398e84dc4f803a2ce11e7ffc0f/django-5.2.1-py3-none-any.whl", hash = "sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961", size = 8301833, upload-time = "2025-05-07T14:06:10.955Z" }, + { url = "https://files.pythonhosted.org/packages/90/92/7448697b5838b3a1c6e1d2d6a673e908d0398e84dc4f803a2ce11e7ffc0f/django-5.2.1-py3-none-any.whl", hash = "sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961", size = 8301833 }, ] [[package]] @@ -71,9 +69,9 @@ dependencies = [ { name = "asgiref" }, { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/6c/16f6cb6064c63074fd5b2bd494eb319afd846236d9c1a6c765946df2c289/django_cors_headers-4.7.0.tar.gz", hash = "sha256:6fdf31bf9c6d6448ba09ef57157db2268d515d94fc5c89a0a1028e1fc03ee52b", size = 21037, upload-time = "2025-02-06T22:15:28.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/6c/16f6cb6064c63074fd5b2bd494eb319afd846236d9c1a6c765946df2c289/django_cors_headers-4.7.0.tar.gz", hash = "sha256:6fdf31bf9c6d6448ba09ef57157db2268d515d94fc5c89a0a1028e1fc03ee52b", size = 21037 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/a2/7bcfff86314bd9dd698180e31ba00604001606efb518a06cca6833a54285/django_cors_headers-4.7.0-py3-none-any.whl", hash = "sha256:f1c125dcd58479fe7a67fe2499c16ee38b81b397463cf025f0e2c42937421070", size = 12794, upload-time = "2025-02-06T22:15:24.341Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a2/7bcfff86314bd9dd698180e31ba00604001606efb518a06cca6833a54285/django_cors_headers-4.7.0-py3-none-any.whl", hash = "sha256:f1c125dcd58479fe7a67fe2499c16ee38b81b397463cf025f0e2c42937421070", size = 12794 }, ] [[package]] @@ -87,18 +85,9 @@ dependencies = [ { name = "setuptools" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/47/b756380917cb4b968bd871fc006128e2cc9897fb1ab4bcf7d108f9601e78/influxdb_client-1.48.0.tar.gz", hash = "sha256:414d5b5eff7d2b6b453f33e2826ea9872ea04a11996ba9c8604b0c1df57c8559", size = 386415, upload-time = "2024-11-27T08:26:32.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/47/b756380917cb4b968bd871fc006128e2cc9897fb1ab4bcf7d108f9601e78/influxdb_client-1.48.0.tar.gz", hash = "sha256:414d5b5eff7d2b6b453f33e2826ea9872ea04a11996ba9c8604b0c1df57c8559", size = 386415 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/b3/1edc89584b8d1bc5226cf508b67ab64da3ba83041cab348861e6f4392326/influxdb_client-1.48.0-py3-none-any.whl", hash = "sha256:410db15db761df7ea98adb333c7a03f05bcc2ceef4830cefb7071b888be2b827", size = 746177, upload-time = "2024-11-27T08:26:30.438Z" }, -] - -[[package]] -name = "paho-mqtt" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b3/1edc89584b8d1bc5226cf508b67ab64da3ba83041cab348861e6f4392326/influxdb_client-1.48.0-py3-none-any.whl", hash = "sha256:410db15db761df7ea98adb333c7a03f05bcc2ceef4830cefb7071b888be2b827", size = 746177 }, ] [[package]] @@ -108,18 +97,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] [[package]] name = "python-dotenv" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, ] [[package]] @@ -129,86 +118,86 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/63/f776322df4d7b456446eff78c4e64f14c3c26d57d46b4e06c18807d5d99c/reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8", size = 119177, upload-time = "2022-07-16T07:11:53.689Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/63/f776322df4d7b456446eff78c4e64f14c3c26d57d46b4e06c18807d5d99c/reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8", size = 119177 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/3f/2ed8c1b8fe3fc2ed816ba40554ef703aad8c51700e2606c139fcf9b7f791/reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a", size = 217791, upload-time = "2022-07-16T07:11:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/2ed8c1b8fe3fc2ed816ba40554ef703aad8c51700e2606c139fcf9b7f791/reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a", size = 217791 }, ] [[package]] name = "ruff" version = "0.11.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" }, - { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" }, - { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" }, - { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" }, - { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" }, - { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" }, - { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" }, - { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" }, - { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" }, - { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" }, - { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 }, + { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 }, + { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 }, + { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 }, + { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 }, + { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 }, + { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 }, + { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 }, + { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 }, + { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 }, + { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 }, + { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 }, + { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 }, + { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 }, + { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 }, ] [[package]] name = "setuptools" version = "80.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188, upload-time = "2025-05-15T02:41:00.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776, upload-time = "2025-05-15T02:40:58.887Z" }, + { url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776 }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "sqlparse" version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, ] [[package]] name = "tzdata" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, ] diff --git a/frontend/package.json b/frontend/package.json index 45a35351100709d0e3fe8a89c0b58ef2ec940204..c4b1d7cb450c333aaf81251ea52b833b9162ed79 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,37 +1,43 @@ { - "name": "frontend", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "run-p type-check \"build-only {@}\" --", - "preview": "vite preview", - "build-only": "vite build", - "type-check": "vue-tsc --build", - "lint": "eslint . --fix", - "format": "prettier --write src/" - }, - "dependencies": { - "pinia": "^3.0.1", - "vue": "^3.5.13", - "vue-router": "^4.5.0" - }, - "devDependencies": { - "@tsconfig/node22": "^22.0.1", - "@types/node": "^22.14.0", - "@vitejs/plugin-vue": "^5.2.3", - "@vue/eslint-config-prettier": "^10.2.0", - "@vue/eslint-config-typescript": "^14.5.0", - "@vue/tsconfig": "^0.7.0", - "eslint": "^9.22.0", - "eslint-plugin-vue": "~10.0.0", - "jiti": "^2.4.2", - "npm-run-all2": "^7.0.2", - "prettier": "3.5.3", - "typescript": "~5.8.0", - "vite": "^6.2.4", - "vite-plugin-vue-devtools": "^7.7.2", - "vue-tsc": "^2.2.8" - } -} + "name": "frontend", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint": "eslint . --fix", + "format": "prettier --write src/" + }, + "dependencies": { + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.1", + "@types/node": "^22.14.0", + "@vitejs/plugin-vue": "^5.2.3", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.5.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.22.0", + "eslint-plugin-vue": "~10.0.0", + "jiti": "^2.4.2", + "npm-run-all2": "^7.0.2", + "prettier": "3.5.3", + "typescript": "~5.8.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2", + "vue-tsc": "^2.2.8" + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true + } +} \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7905b05167cb53db655f3938ea2adb68ca72e9fb..c8b3ee78215271c0911d7f8eb3254829c8d08db0 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,82 +4,84 @@ import HelloWorld from './components/HelloWorld.vue' </script> <template> - <header> - <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" /> - - <div class="wrapper"> - <HelloWorld msg="You did it!" /> - - <nav> - <RouterLink to="/">Home</RouterLink> - <RouterLink to="/about">About</RouterLink> - </nav> - </div> - </header> - - <RouterView /> + <header> + <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" /> + + <div class="wrapper"> + <!-- <HelloWorld msg="You did it!" /> --> + + <nav> + <RouterLink to="/">Home</RouterLink> + <RouterLink to="/login">Login</RouterLink> + <RouterLink to="/about">About</RouterLink> + <RouterLink to="/register">register</RouterLink> + </nav> + </div> + </header> + + <RouterView /> </template> <style scoped> header { - line-height: 1.5; - max-height: 100vh; + line-height: 1.5; + max-height: 100vh; } .logo { - display: block; - margin: 0 auto 2rem; + display: block; + margin: 0 auto 2rem; } nav { - width: 100%; - font-size: 12px; - text-align: center; - margin-top: 2rem; + width: 100%; + font-size: 12px; + text-align: center; + margin-top: 2rem; } nav a.router-link-exact-active { - color: var(--color-text); + color: var(--color-text); } nav a.router-link-exact-active:hover { - background-color: transparent; + background-color: transparent; } nav a { - display: inline-block; - padding: 0 1rem; - border-left: 1px solid var(--color-border); + display: inline-block; + padding: 0 1rem; + border-left: 1px solid var(--color-border); } nav a:first-of-type { - border: 0; + border: 0; } @media (min-width: 1024px) { - header { - display: flex; - place-items: center; - padding-right: calc(var(--section-gap) / 2); - } - - .logo { - margin: 0 2rem 0 0; - } - - header .wrapper { - display: flex; - place-items: flex-start; - flex-wrap: wrap; - } - - nav { - text-align: left; - margin-left: -1rem; - font-size: 1rem; - - padding: 1rem 0; - margin-top: 1rem; - } + header { + display: flex; + place-items: center; + padding-right: calc(var(--section-gap) / 2); + } + + .logo { + margin: 0 2rem 0 0; + } + + header .wrapper { + display: flex; + place-items: flex-start; + flex-wrap: wrap; + } + + nav { + text-align: left; + margin-left: -1rem; + font-size: 1rem; + + padding: 1rem 0; + margin-top: 1rem; + } } </style> diff --git a/frontend/src/assets/base.css b/frontend/src/assets/base.css index 8816868a41b651f318dee87c6784ebcd6e29eca1..8710b9aeab5be4a2fa828acca9b749e0f08fd34f 100644 --- a/frontend/src/assets/base.css +++ b/frontend/src/assets/base.css @@ -1,86 +1,86 @@ /* color palette from <https://github.com/vuejs/theme> */ :root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; - --vt-c-indigo: #2c3e50; + --vt-c-indigo: #2c3e50; - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); } /* semantic color variables for this project */ :root { - --color-background: var(--vt-c-white); - --color-background-soft: var(--vt-c-white-soft); - --color-background-mute: var(--vt-c-white-mute); + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); - --color-border: var(--vt-c-divider-light-2); - --color-border-hover: var(--vt-c-divider-light-1); + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); - --color-heading: var(--vt-c-text-light-1); - --color-text: var(--vt-c-text-light-1); + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); - --section-gap: 160px; + --section-gap: 160px; } @media (prefers-color-scheme: dark) { - :root { - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - } + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } } *, *::before, *::after { - box-sizing: border-box; - margin: 0; - font-weight: normal; + box-sizing: border-box; + margin: 0; + font-weight: normal; } body { - min-height: 100vh; - color: var(--color-text); - background: var(--color-background); - transition: - color 0.5s, - background-color 0.5s; - line-height: 1.6; - font-family: - Inter, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Oxygen, - Ubuntu, - Cantarell, - 'Fira Sans', - 'Droid Sans', - 'Helvetica Neue', - sans-serif; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 36fb845b5232b8594b0d0f0e61a8cff0b73a4128..2fe73bb66673ff03d73a88d61e71e05b4cead722 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -1,35 +1,35 @@ @import './base.css'; #app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - font-weight: normal; + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; } a, .green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; - padding: 3px; + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; } @media (hover: hover) { - a:hover { - background-color: hsla(160, 100%, 37%, 0.2); - } + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } } @media (min-width: 1024px) { - body { - display: flex; - place-items: center; - } + body { + display: flex; + place-items: center; + } - #app { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 0 2rem; - } + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } } diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue index d174cf8e1c12827184281d516d457c6d914c4340..b384008861dafa1610f6ff9e2bc74806584f386f 100644 --- a/frontend/src/components/HelloWorld.vue +++ b/frontend/src/components/HelloWorld.vue @@ -1,41 +1,41 @@ <script setup lang="ts"> defineProps<{ - msg: string + msg: string }>() </script> <template> - <div class="greetings"> - <h1 class="green">{{ msg }}</h1> - <h3> - You’ve successfully created a project with - <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> + - <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next? - </h3> - </div> + <div class="greetings"> + <h1 class="green">{{ msg }}</h1> + <h3> + You’ve successfully created a project with + <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> + + <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next? + </h3> + </div> </template> <style scoped> h1 { - font-weight: 500; - font-size: 2.6rem; - position: relative; - top: -10px; + font-weight: 500; + font-size: 2.6rem; + position: relative; + top: -10px; } h3 { - font-size: 1.2rem; + font-size: 1.2rem; } .greetings h1, .greetings h3 { - text-align: center; + text-align: center; } @media (min-width: 1024px) { - .greetings h1, - .greetings h3 { - text-align: left; - } + .greetings h1, + .greetings h3 { + text-align: left; + } } </style> diff --git a/frontend/src/components/TheWelcome.vue b/frontend/src/components/TheWelcome.vue index 8b731d918bc5236b399d7e73d3602509ad693c89..cbfd48692ce48d28d0f71c4c5ad009077870dedb 100644 --- a/frontend/src/components/TheWelcome.vue +++ b/frontend/src/components/TheWelcome.vue @@ -10,86 +10,89 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') </script> <template> - <WelcomeItem> - <template #icon> - <DocumentationIcon /> - </template> - <template #heading>Documentation</template> + <WelcomeItem> + <template #icon> + <DocumentationIcon /> + </template> + <template #heading>Documentation</template> - Vue’s - <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a> - provides you with all information you need to get started. - </WelcomeItem> + Vue’s + <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a> + provides you with all information you need to get started. + </WelcomeItem> - <WelcomeItem> - <template #icon> - <ToolingIcon /> - </template> - <template #heading>Tooling</template> + <WelcomeItem> + <template #icon> + <ToolingIcon /> + </template> + <template #heading>Tooling</template> - This project is served and bundled with - <a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The - recommended IDE setup is - <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> - + - <a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener" - >Vue - Official</a - >. If you need to test your components and web pages, check out - <a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a> - and - <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> - / - <a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>. + This project is served and bundled with + <a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The + recommended IDE setup is + <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> + + + <a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener" + >Vue - Official</a + >. If you need to test your components and web pages, check out + <a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a> + and + <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> + / + <a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>. - <br /> + <br /> - More instructions are available in - <a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a - >. - </WelcomeItem> + More instructions are available in + <a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a + >. + </WelcomeItem> - <WelcomeItem> - <template #icon> - <EcosystemIcon /> - </template> - <template #heading>Ecosystem</template> + <WelcomeItem> + <template #icon> + <EcosystemIcon /> + </template> + <template #heading>Ecosystem</template> - Get official tools and libraries for your project: - <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>, - <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>, - <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and - <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If - you need more resources, we suggest paying - <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a> - a visit. - </WelcomeItem> + Get official tools and libraries for your project: + <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>, + <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>, + <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, + and + <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a + >. If you need more resources, we suggest paying + <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener" + >Awesome Vue</a + > + a visit. + </WelcomeItem> - <WelcomeItem> - <template #icon> - <CommunityIcon /> - </template> - <template #heading>Community</template> + <WelcomeItem> + <template #icon> + <CommunityIcon /> + </template> + <template #heading>Community</template> - Got stuck? Ask your question on - <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a> - (our official Discord server), or - <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener" - >StackOverflow</a - >. You should also follow the official - <a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a> - Bluesky account or the - <a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a> - X account for latest news in the Vue world. - </WelcomeItem> + Got stuck? Ask your question on + <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a> + (our official Discord server), or + <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener" + >StackOverflow</a + >. You should also follow the official + <a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a> + Bluesky account or the + <a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a> + X account for latest news in the Vue world. + </WelcomeItem> - <WelcomeItem> - <template #icon> - <SupportIcon /> - </template> - <template #heading>Support Vue</template> + <WelcomeItem> + <template #icon> + <SupportIcon /> + </template> + <template #heading>Support Vue</template> - As an independent project, Vue relies on community backing for its sustainability. You can help - us by - <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>. - </WelcomeItem> + As an independent project, Vue relies on community backing for its sustainability. You can + help us by + <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>. + </WelcomeItem> </template> diff --git a/frontend/src/components/WelcomeItem.vue b/frontend/src/components/WelcomeItem.vue index 6d7086aea08fbb770b2f570dbeb4f8fcd86cb291..8325ed136eac64f62d07f396930a2fa9fc6a15be 100644 --- a/frontend/src/components/WelcomeItem.vue +++ b/frontend/src/components/WelcomeItem.vue @@ -1,87 +1,87 @@ <template> - <div class="item"> - <i> - <slot name="icon"></slot> - </i> - <div class="details"> - <h3> - <slot name="heading"></slot> - </h3> - <slot></slot> + <div class="item"> + <i> + <slot name="icon"></slot> + </i> + <div class="details"> + <h3> + <slot name="heading"></slot> + </h3> + <slot></slot> + </div> </div> - </div> </template> <style scoped> .item { - margin-top: 2rem; - display: flex; - position: relative; + margin-top: 2rem; + display: flex; + position: relative; } .details { - flex: 1; - margin-left: 1rem; + flex: 1; + margin-left: 1rem; } i { - display: flex; - place-items: center; - place-content: center; - width: 32px; - height: 32px; + display: flex; + place-items: center; + place-content: center; + width: 32px; + height: 32px; - color: var(--color-text); + color: var(--color-text); } h3 { - font-size: 1.2rem; - font-weight: 500; - margin-bottom: 0.4rem; - color: var(--color-heading); + font-size: 1.2rem; + font-weight: 500; + margin-bottom: 0.4rem; + color: var(--color-heading); } @media (min-width: 1024px) { - .item { - margin-top: 0; - padding: 0.4rem 0 1rem calc(var(--section-gap) / 2); - } + .item { + margin-top: 0; + padding: 0.4rem 0 1rem calc(var(--section-gap) / 2); + } - i { - top: calc(50% - 25px); - left: -26px; - position: absolute; - border: 1px solid var(--color-border); - background: var(--color-background); - border-radius: 8px; - width: 50px; - height: 50px; - } + i { + top: calc(50% - 25px); + left: -26px; + position: absolute; + border: 1px solid var(--color-border); + background: var(--color-background); + border-radius: 8px; + width: 50px; + height: 50px; + } - .item:before { - content: ' '; - border-left: 1px solid var(--color-border); - position: absolute; - left: 0; - bottom: calc(50% + 25px); - height: calc(50% - 25px); - } + .item:before { + content: ' '; + border-left: 1px solid var(--color-border); + position: absolute; + left: 0; + bottom: calc(50% + 25px); + height: calc(50% - 25px); + } - .item:after { - content: ' '; - border-left: 1px solid var(--color-border); - position: absolute; - left: 0; - top: calc(50% + 25px); - height: calc(50% - 25px); - } + .item:after { + content: ' '; + border-left: 1px solid var(--color-border); + position: absolute; + left: 0; + top: calc(50% + 25px); + height: calc(50% - 25px); + } - .item:first-of-type:before { - display: none; - } + .item:first-of-type:before { + display: none; + } - .item:last-of-type:after { - display: none; - } + .item:last-of-type:after { + display: none; + } } </style> diff --git a/frontend/src/components/icons/IconCommunity.vue b/frontend/src/components/icons/IconCommunity.vue index 2dc8b055253af30fb797037e2fe260505f0cf711..36348e2ee2939606a832d4fd1b6353646af45f5f 100644 --- a/frontend/src/components/icons/IconCommunity.vue +++ b/frontend/src/components/icons/IconCommunity.vue @@ -1,7 +1,7 @@ <template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> - <path - d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" - /> - </svg> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> + <path + d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" + /> + </svg> </template> diff --git a/frontend/src/components/icons/IconDocumentation.vue b/frontend/src/components/icons/IconDocumentation.vue index 6d4791cfbcf2782b3e5ffbabd042d4c47b2fbbed..0c0fa7610b34b2f5853f7d2c3143d2ff6c9ee1b4 100644 --- a/frontend/src/components/icons/IconDocumentation.vue +++ b/frontend/src/components/icons/IconDocumentation.vue @@ -1,7 +1,7 @@ <template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor"> - <path - d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" - /> - </svg> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor"> + <path + d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" + /> + </svg> </template> diff --git a/frontend/src/components/icons/IconEcosystem.vue b/frontend/src/components/icons/IconEcosystem.vue index c3a4f078c0bd340a33c61ea9ecd8a755d03571ed..0702bbbe16bbbe88654ec119dc307cd678ea6d63 100644 --- a/frontend/src/components/icons/IconEcosystem.vue +++ b/frontend/src/components/icons/IconEcosystem.vue @@ -1,7 +1,7 @@ <template> - <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor"> - <path - d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" - /> - </svg> + <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor"> + <path + d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" + /> + </svg> </template> diff --git a/frontend/src/components/icons/IconSupport.vue b/frontend/src/components/icons/IconSupport.vue index 7452834d3ef961ce24c3a072ddba2620b6158bae..908b94c4f8b389565dd8511113b74b7a34d276b8 100644 --- a/frontend/src/components/icons/IconSupport.vue +++ b/frontend/src/components/icons/IconSupport.vue @@ -1,7 +1,7 @@ <template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> - <path - d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" - /> - </svg> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> + <path + d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" + /> + </svg> </template> diff --git a/frontend/src/components/icons/IconTooling.vue b/frontend/src/components/icons/IconTooling.vue index 660598d7c76644ffe126a1a1feb1606650bfb937..9e82068426eb6cd0d5b859faa5c9ea3150be7815 100644 --- a/frontend/src/components/icons/IconTooling.vue +++ b/frontend/src/components/icons/IconTooling.vue @@ -1,19 +1,19 @@ <!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license--> <template> - <svg - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - aria-hidden="true" - role="img" - class="iconify iconify--mdi" - width="24" - height="24" - preserveAspectRatio="xMidYMid meet" - viewBox="0 0 24 24" - > - <path - d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z" - fill="currentColor" - ></path> - </svg> + <svg + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + aria-hidden="true" + role="img" + class="iconify iconify--mdi" + width="24" + height="24" + preserveAspectRatio="xMidYMid meet" + viewBox="0 0 24 24" + > + <path + d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z" + fill="currentColor" + ></path> + </svg> </template> diff --git a/frontend/src/main.ts b/frontend/src/main.ts index b324e20e156cbfc5a18ac6107dc6b5e5d81d5902..0817cbb08f74aa8859847f7f957fc3af732cf837 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -2,7 +2,7 @@ import './assets/main.css' import { createApp } from 'vue' import { createPinia } from 'pinia' -import { useAuthStore } from './store/auth' +import { useAuthStore } from './stores/auth' import App from './App.vue' import router from './router' diff --git a/frontend/src/shims-vue.d.ts b/frontend/src/shims-vue.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f231ed1118ca99c68ec51cee12cd13ed686b6c1 --- /dev/null +++ b/frontend/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c527aca28f6182b40501759e5e2f7a1b7e5f82e --- /dev/null +++ b/frontend/src/stores/auth.ts @@ -0,0 +1,152 @@ +import { defineStore } from 'pinia' +import { type Router } from 'vue-router' + +interface AuthState { + user: User | null + isAuthenticated: boolean +} + +interface User { + id: number + email: string + [key: string]: any // Add more fields as needed +} + +export const useAuthStore = defineStore('auth', { + state: (): AuthState => { + const storedState = localStorage.getItem('authState') + return storedState + ? JSON.parse(storedState) + : { + user: null, + isAuthenticated: false, + } + }, + actions: { + async setCsrfToken(): Promise<void> { + await fetch('http://localhost:8000/api/set-csrf-token', { + method: 'GET', + credentials: 'include', + }) + }, + + async login(email: string, password: string, router: Router | null = null): Promise<void> { + const response = await fetch('http://localhost:8000/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + body: JSON.stringify({ + email, + password, + }), + credentials: 'include', + }) + const data = await response.json() + if (data.success) { + this.isAuthenticated = true + this.saveState() + if (router) { + await router.push({ + name: 'home', + }) + } + } else { + this.user = null + this.isAuthenticated = false + this.saveState() + } + }, + + async logout(router: Router | null = null): Promise<void> { + try { + const response = await fetch('http://localhost:8000/api/logout', { + method: 'POST', + headers: { + 'X-CSRFToken': getCSRFToken(), + }, + credentials: 'include', + }) + if (response.ok) { + this.user = null + this.isAuthenticated = false + this.saveState() + if (router) { + await router.push({ + name: 'login', + }) + } + } + } catch (error) { + console.error('Logout failed', error) + throw error + } + }, + + async fetchUser(): Promise<void> { + try { + const response = await fetch('http://localhost:8000/api/user', { + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + }) + if (response.ok) { + const data: User = await response.json() + this.user = data + this.isAuthenticated = true + } else { + this.user = null + this.isAuthenticated = false + } + } catch (error) { + console.error('Failed to fetch user', error) + this.user = null + this.isAuthenticated = false + } + this.saveState() + }, + + saveState(): void { + /* + We save state to local storage to keep the + state when the user reloads the page. + + This is a simple way to persist state. For a more robust solution, + use pinia-persistent-state. + */ + localStorage.setItem( + 'authState', + JSON.stringify({ + user: this.user, + isAuthenticated: this.isAuthenticated, + }), + ) + }, + }, +}) + +export function getCSRFToken(): string { + /* + We get the CSRF token from the cookie to include in our requests. + This is necessary for CSRF protection in Django. + */ + const name = 'csrftoken' + let cookieValue: string | null = null + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';') + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim() + if (cookie.substring(0, name.length + 1) === name + '=') { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) + break + } + } + } + if (cookieValue === null) { + throw new Error('Missing CSRF cookie.') + } + return cookieValue +} \ No newline at end of file diff --git a/frontend/src/stores/authold.js b/frontend/src/stores/authold.js new file mode 100644 index 0000000000000000000000000000000000000000..1e9d7c744e4ebb741a31a75116ec2c96e0fc3714 --- /dev/null +++ b/frontend/src/stores/authold.js @@ -0,0 +1,140 @@ +import { defineStore } from 'pinia' + +export const useAuthStore = defineStore('auth', { + state: () => { + const storedState = localStorage.getItem('authState') + return storedState + ? JSON.parse(storedState) + : { + user: null, + isAuthenticated: false, + } + }, + actions: { + async setCsrfToken() { + await fetch('http://localhost:8000/api/set-csrf-token', { + method: 'GET', + credentials: 'include', + }) + }, + + async login(email, password, router = null) { + const response = await fetch('http://localhost:8000/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + body: JSON.stringify({ + email, + password, + }), + credentials: 'include', + }) + const data = await response.json() + if (data.success) { + this.isAuthenticated = true + this.saveState() + if (router) { + await router.push({ + name: 'home', + }) + } + } else { + this.user = null + this.isAuthenticated = false + this.saveState() + } + }, + + async logout(router = null) { + try { + const response = await fetch('http://localhost:8000/api/logout', { + method: 'POST', + headers: { + 'X-CSRFToken': getCSRFToken(), + }, + credentials: 'include', + }) + if (response.ok) { + this.user = null + this.isAuthenticated = false + this.saveState() + if (router) { + await router.push({ + name: 'login', + }) + } + } + } catch (error) { + console.error('Logout failed', error) + throw error + } + }, + + async fetchUser() { + try { + const response = await fetch('http://localhost:8000/api/user', { + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + }) + if (response.ok) { + const data = await response.json() + this.user = data + this.isAuthenticated = true + } else { + this.user = null + this.isAuthenticated = false + } + } catch (error) { + console.error('Failed to fetch user', error) + this.user = null + this.isAuthenticated = false + } + this.saveState() + }, + + saveState() { + /* + We save state to local storage to keep the + state when the user reloads the page. + + This is a simple way to persist state. For a more robust solution, + use pinia-persistent-state. + */ + localStorage.setItem( + 'authState', + JSON.stringify({ + user: this.user, + isAuthenticated: this.isAuthenticated, + }), + ) + }, + }, +}) + +export function getCSRFToken() { + /* + We get the CSRF token from the cookie to include in our requests. + This is necessary for CSRF protection in Django. + */ + const name = 'csrftoken' + let cookieValue = null + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';') + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim() + if (cookie.substring(0, name.length + 1) === name + '=') { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) + break + } + } + } + if (cookieValue === null) { + throw 'Missing CSRF cookie.' + } + return cookieValue +} \ No newline at end of file diff --git a/frontend/src/stores/counter.ts b/frontend/src/stores/counter.ts index b6757ba5723c5b89b35d011b9558d025bbcde402..5cdda009a5449a1423b51eb25007a4fb12a82b9b 100644 --- a/frontend/src/stores/counter.ts +++ b/frontend/src/stores/counter.ts @@ -2,11 +2,11 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', () => { - const count = ref(0) - const doubleCount = computed(() => count.value * 2) - function increment() { - count.value++ - } + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } - return { count, doubleCount, increment } + return { count, doubleCount, increment } }) diff --git a/frontend/src/views/AboutView.vue b/frontend/src/views/AboutView.vue index 756ad2a17909837834858538422308120cf09dab..f470365d8ba7815af68c71a41ace7937383b4a70 100644 --- a/frontend/src/views/AboutView.vue +++ b/frontend/src/views/AboutView.vue @@ -1,15 +1,15 @@ <template> - <div class="about"> - <h1>This is an about page</h1> - </div> + <div class="about"> + <h1>This is an about page</h1> + </div> </template> <style> @media (min-width: 1024px) { - .about { - min-height: 100vh; - display: flex; - align-items: center; - } + .about { + min-height: 100vh; + display: flex; + align-items: center; + } } </style> diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index d5c0217e45ce8f933917195f236f1d118b317e86..7fe20c308f94db109016634835d1b8bd8771438d 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -1,9 +1,38 @@ -<script setup lang="ts"> -import TheWelcome from '../components/TheWelcome.vue' +<script lang="ts"> +import { useAuthStore } from '../stores/auth' +import { useRouter } from 'vue-router' + +export default { + setup() { + const authStore = useAuthStore() + const router = useRouter() + + return { + authStore, + router, + } + }, + methods: { + async logout() { + try { + await this.authStore.logout(this.$router) + } catch (error) { + console.error(error) + } + }, + }, + async mounted() { + await this.authStore.fetchUser() + }, +} </script> <template> - <main> - <TheWelcome /> - </main> + <h1>Welcome to the home page</h1> + <div v-if="authStore.isAuthenticated"> + <p>Hi there {{ authStore.user?.username }}!</p> + <p>You are logged in .</p> + <button @click="logout">Logout</button> + </div> + <p v-else>You are not logged in . <router-link to="/login"> Login </router-link></p> </template> diff --git a/frontend/src/views/HomeViewOLD.vue b/frontend/src/views/HomeViewOLD.vue new file mode 100644 index 0000000000000000000000000000000000000000..c3b238e54376195f5846bf62160937ade7b6ab90 --- /dev/null +++ b/frontend/src/views/HomeViewOLD.vue @@ -0,0 +1,9 @@ +<script setup lang="ts"> +import TheWelcome from '../components/TheWelcome.vue' +</script> + +<template> + <main> + <TheWelcome /> + </main> +</template> diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue new file mode 100644 index 0000000000000000000000000000000000000000..ebef12a3d2d73ab87e90f814faa90cbbab0f90bb --- /dev/null +++ b/frontend/src/views/LoginView.vue @@ -0,0 +1,55 @@ +<script lang="ts"> +import { useAuthStore } from '../stores/auth' + +export default { + setup() { + const authStore = useAuthStore() + return { + authStore, + } + }, + data() { + return { + email: '', + password: '', + error: '', + } + }, + methods: { + async login() { + await this.authStore.login(this.email, this.password, this.$router) + if (!this.authStore.isAuthenticated) { + this.error = 'Login failed. Please check your credentials.' + } + }, + resetError() { + this.error = '' + }, + }, +} +</script> + +<template> + <div class="login"> + <h1>Login</h1> + <form @submit.prevent="login"> + <div> + <label for="email"> Email: </label> + <input v-model="email" id="email" type="text" required @input="resetError" /> + </div> + <div> + <label for="password"> Password: </label> + <input + v-model="password" + id="password" + type="password" + required + @input="resetError" + /> + </div> + <button type="submit">Login</button> + </form> + <p v-if="error" class="error">{{ error }}</p> + </div> + <p v-if="error" class="error">{{ error }}</p> +</template> diff --git a/frontend/src/views/RegisterView.vue b/frontend/src/views/RegisterView.vue new file mode 100644 index 0000000000000000000000000000000000000000..4f754e98aa9563a684ce0b5ad79c88f754d89393 --- /dev/null +++ b/frontend/src/views/RegisterView.vue @@ -0,0 +1,62 @@ +<script lang="ts"> +import { getCSRFToken } from '../stores/auth' + +export default { + data() { + return { + email: '', + password: '', + error: '', + success: '', + } + }, + methods: { + async register() { + try { + const response = await fetch('http://localhost:8000/api/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + body: JSON.stringify({ + email: this.email, + password: this.password, + }), + credentials: 'include', + }) + const data = await response.json() + if (response.ok) { + this.success = 'Registration successful! Please log in.' + setTimeout(() => { + this.$router.push('/login') + }, 1000) + } else { + this.error = data.error || 'Registration failed' + } + } catch (err) { + this.error = 'An error occurred during registration: ' + err + } + }, + }, +} +</script> + +<template> + <div> + <h2>Register</h2> + <form @submit.prevent="register"> + <div> + <label for="email"> Email: </label> + <input v-model="email" id="email" type="email" required /> + </div> + <div> + <label for="password"> Password: </label> + <input v-model="password" id="password" type="password" required /> + </div> + <button type="submit">Register</button> + </form> + <p v-if="error">{{ error }}</p> + <p v-if="success">{{ success }}</p> + </div> +</template>