From 771a6f8432ef1fefbc8c0f88e5c5bb99c06f53d2 Mon Sep 17 00:00:00 2001 From: oleh Date: Sun, 9 Feb 2025 16:50:33 +0100 Subject: [PATCH] add register/login/database on aws --- Backend/__pycache__/model.cpython-311.pyc | Bin 13973 -> 13973 bytes Backend/server.py | 129 +++++++++- frontend/package-lock.json | 188 ++++++-------- frontend/package.json | 4 +- frontend/src/App.tsx | 4 + frontend/src/Components/LoginForm.tsx | 190 +++++++++++++++ frontend/src/Components/Navigation.tsx | 117 +++++---- frontend/src/Components/RegistrationForm.tsx | 243 +++++++++++++++++++ frontend/src/pages/LandingPage.tsx | 168 +++++++++---- 9 files changed, 836 insertions(+), 207 deletions(-) create mode 100644 frontend/src/Components/LoginForm.tsx create mode 100644 frontend/src/Components/RegistrationForm.tsx diff --git a/Backend/__pycache__/model.cpython-311.pyc b/Backend/__pycache__/model.cpython-311.pyc index 9be7682490254ea18748c647fddacd7b5c153a91..dcfc304c4cb6c174f5441b19d04e7663492d7b18 100644 GIT binary patch delta 20 acmbQ5J2jVkIWI340}vcvx_BdZml*&;g9c^* delta 20 acmbQ5J2jVkIWI340}wP#Sg?`1%M1WO69x$Y diff --git a/Backend/server.py b/Backend/server.py index 047ed5b..db5cb4a 100644 --- a/Backend/server.py +++ b/Backend/server.py @@ -1,19 +1,144 @@ +import time +# Сохраняем оригинальную функцию time.time +_real_time = time.time +# Переопределяем time.time для смещения времени на 1 секунду назад +time.time = lambda: _real_time() - 1 + from flask import Flask, request, jsonify from flask_cors import CORS +from google.oauth2 import id_token +from google.auth.transport import requests import logging # Импортируем функцию обработки из model.py from model import process_query_with_mistral +import psycopg2 +from psycopg2.extras import RealDictCursor + +# Параметры подключения +DATABASE_CONFIG = { + "dbname": "postgres", + "user": "postgres", + "password": "healthai!", + "host": "health-ai-user-db.cxeum6cmct3r.eu-west-1.rds.amazonaws.com", + "port": 5432, +} + +# Подключение к базе данных +try: + conn = psycopg2.connect(**DATABASE_CONFIG) + cursor = conn.cursor(cursor_factory=RealDictCursor) + print("Подключение к базе данных успешно установлено") +except Exception as e: + print(f"Ошибка подключения к базе данных: {e}") + conn = None logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Создаем Flask приложение app = Flask(__name__) -CORS(app) # Разрешаем CORS для всех доменов CORS(app, resources={r"/api/*": {"origins": "http://localhost:5173"}}) -# Маршрут для обработки запросов от фронтенда +# Ваш Google Client ID +CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com" + +def save_user_to_db(name, email, google_id=None, password=None): + try: + cursor.execute( + """ + INSERT INTO users (name, email, google_id, password) + VALUES (%s, %s, %s, %s) + ON CONFLICT (email) DO NOTHING + """, + (name, email, google_id, password) + ) + conn.commit() + print(f"User {name} ({email}) saved successfully!") + except Exception as e: + print(f"Error saving user to database: {e}") + +# Эндпоинт для верификации токенов Google OAuth +@app.route('/api/verify', methods=['POST']) +def verify_token(): + data = request.get_json() + token = data.get('token') + + if not token: + return jsonify({'error': 'No token provided'}), 400 + + try: + id_info = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID) + user_email = id_info.get('email') + user_name = id_info.get('name') + google_id = id_info.get('sub') # Уникальный идентификатор пользователя Google + + save_user_to_db(name=user_name, email=user_email, google_id=google_id) + + logger.info(f"User authenticated and saved: {user_name} ({user_email})") + return jsonify({'message': 'Authentication successful', 'user': {'email': user_email, 'name': user_name}}), 200 + + except ValueError as e: + logger.error(f"Token verification failed: {e}") + return jsonify({'error': 'Invalid token'}), 400 + +# Эндпоинт для регистрации пользователя +@app.route('/api/register', methods=['POST']) +def register(): + data = request.get_json() + name = data.get('name') + email = data.get('email') + password = data.get('password') # Рекомендуется хэшировать пароль + + if not all([name, email, password]): + return jsonify({'error': 'All fields are required'}), 400 + + try: + # Проверка, существует ли пользователь с таким email + cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) + existing_user = cursor.fetchone() + if existing_user: + return jsonify({'error': 'User already exists'}), 409 + + # Сохранение пользователя в базу данных + save_user_to_db(name=name, email=email, password=password) + + return jsonify({'message': 'User registered successfully'}), 201 + except Exception as e: + return jsonify({'error': str(e)}), 500 + +# Эндпоинт для логина пользователя (см. предыдущий пример) +@app.route('/api/login', methods=['POST']) +def login(): + data = request.get_json() + email = data.get('email') + password = data.get('password') + + if not all([email, password]): + return jsonify({'error': 'Email and password are required'}), 400 + + try: + cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) + user = cursor.fetchone() + + if not user: + return jsonify({'error': 'Invalid credentials'}), 401 + + # Сравнение простым текстом — в production используйте хэширование! + if user.get('password') != password: + return jsonify({'error': 'Invalid credentials'}), 401 + + return jsonify({ + 'message': 'Login successful', + 'user': { + 'name': user.get('name'), + 'email': user.get('email') + } + }), 200 + except Exception as e: + return jsonify({'error': str(e)}), 500 + +# Эндпоинт для обработки запросов от фронтенда @app.route('/api/chat', methods=['POST']) def chat(): data = request.get_json() diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f5524e2..3e11ca0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,9 +11,9 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@gsap/react": "^2.1.1", - "@material-ui/icons": "^4.11.3", - "@mui/icons-material": "^6.1.5", + "@mui/icons-material": "^6.4.3", "@mui/material": "^6.1.5", + "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.3.0", "appwrite": "^16.0.2", "final-form": "^4.20.10", @@ -379,14 +379,13 @@ "license": "MIT" }, "node_modules/@emotion/cache": { - "version": "11.13.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", - "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", - "license": "MIT", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.0", + "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } @@ -437,15 +436,14 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", - "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", - "license": "MIT", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.1", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, @@ -494,10 +492,9 @@ } }, "node_modules/@emotion/utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", - "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", - "license": "MIT" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", @@ -1158,45 +1155,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@material-ui/icons": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", - "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", - "dependencies": { - "@babel/runtime": "^7.4.4" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@material-ui/core": "^4.0.0", - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.5.tgz", - "integrity": "sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz", + "integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.5.tgz", - "integrity": "sha512-SbxFtO5I4cXfvhjAMgGib/t2lQUzcEzcDFYiRHRufZUeMMeXuoKaGsptfwAHTepYkv0VqcCwvxtvtWbpZLAbjQ==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.3.tgz", + "integrity": "sha512-3IY9LpjkwIJVgL/SkZQKKCUcumdHdQEsJaIavvsQze2QEztBt0HJ17naToN0DBBdhKdtwX5xXrfD6ZFUeWWk8g==", "dependencies": { - "@babel/runtime": "^7.25.7" + "@babel/runtime": "^7.26.0" }, "engines": { "node": ">=14.0.0" @@ -1206,7 +1179,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.5", + "@mui/material": "^6.4.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1217,22 +1190,21 @@ } }, "node_modules/@mui/material": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.5.tgz", - "integrity": "sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz", + "integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==", "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/core-downloads-tracker": "^6.1.5", - "@mui/system": "^6.1.5", - "@mui/types": "^7.2.18", - "@mui/utils": "^6.1.5", + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.3", + "@mui/system": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.11", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.3.1", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -1245,7 +1217,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.5", + "@mui/material-pigment-css": "^6.4.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1266,13 +1238,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.5.tgz", - "integrity": "sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", + "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^6.1.5", + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.3", "prop-types": "^15.8.1" }, "engines": { @@ -1293,14 +1264,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.5.tgz", - "integrity": "sha512-tiyWzMkHeWlOoE6AqomWvYvdml8Nv5k5T+LDwOiwHEawx8P9Lyja6ZwWPU6xljwPXYYPT2KBp1XvMly7dsK46A==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", + "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", "dependencies": { - "@babel/runtime": "^7.25.7", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1327,16 +1297,15 @@ } }, "node_modules/@mui/system": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.5.tgz", - "integrity": "sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", + "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/private-theming": "^6.1.5", - "@mui/styled-engine": "^6.1.5", - "@mui/types": "^7.2.18", - "@mui/utils": "^6.1.5", + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.3", + "@mui/styled-engine": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1367,10 +1336,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.18", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", - "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", - "license": "MIT", + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1381,17 +1349,16 @@ } }, "node_modules/@mui/utils": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.5.tgz", - "integrity": "sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==", - "license": "MIT", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", + "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/types": "^7.2.18", - "@types/prop-types": "^15.7.13", + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.3.1" + "react-is": "^19.0.0" }, "engines": { "node": ">=14.0.0" @@ -1469,6 +1436,15 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-oauth/google": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz", + "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", @@ -1792,10 +1768,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "license": "MIT" + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/react": { "version": "18.3.12", @@ -1818,11 +1793,10 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", - "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", - "license": "MIT", - "dependencies": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { "@types/react": "*" } }, @@ -2433,7 +2407,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", "engines": { "node": ">=6" } @@ -4099,10 +4072,9 @@ } }, "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" }, "node_modules/react-redux": { "version": "9.1.2", diff --git a/frontend/package.json b/frontend/package.json index 3781d0c..72cb2af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,9 +13,9 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@gsap/react": "^2.1.1", - "@material-ui/icons": "^4.11.3", - "@mui/icons-material": "^6.1.5", + "@mui/icons-material": "^6.4.3", "@mui/material": "^6.1.5", + "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.3.0", "appwrite": "^16.0.2", "final-form": "^4.20.10", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8705411..589728b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,6 +2,8 @@ import { BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom import Navigation from './Components/Navigation'; import HomePage from './pages/HomePage'; import LandingPage from './pages/LandingPage'; +import RegistrationForm from "./Components/RegistrationForm.tsx"; +import LoginForm from "./Components/LoginForm.tsx"; const Layout = () => ( @@ -21,6 +23,8 @@ function App() { } /> + } /> + } /> Sorry not implemented yet} /> Sorry not implemented yet} /> Sorry not implemented yet} /> diff --git a/frontend/src/Components/LoginForm.tsx b/frontend/src/Components/LoginForm.tsx new file mode 100644 index 0000000..6c22c76 --- /dev/null +++ b/frontend/src/Components/LoginForm.tsx @@ -0,0 +1,190 @@ +import React from 'react'; +import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google'; +import { Link, useNavigate } from 'react-router-dom'; + +const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"; + +const LoginFormContent: React.FC = () => { + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Приведение типов для получения значений инпутов + const emailElement = document.getElementById('email') as HTMLInputElement | null; + const passwordElement = document.getElementById('password') as HTMLInputElement | null; + + if (!emailElement || !passwordElement) { + console.error('Один или несколько инпутов отсутствуют'); + return; + } + + const email = emailElement.value; + const password = passwordElement.value; + + try { + const response = await fetch('http://localhost:5000/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + + const data = await response.json(); + + if (response.ok) { + console.log('Login successful:', data.message); + const loggedInUser = { + name: data.user.name, + email: data.user.email, + picture: data.user.picture || 'https://via.placeholder.com/150', + }; + localStorage.setItem('user', JSON.stringify(loggedInUser)); + navigate('/dashboard'); + } else { + console.error('Ошибка:', data.error); + } + } catch (error) { + console.error('Ошибка при входе:', error); + } + }; + + const handleGoogleLoginSuccess = async (response: any) => { + try { + const res = await fetch('http://localhost:5000/api/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: response.credential }), + }); + const data = await res.json(); + const loggedInUser = { + name: data.user.name, + email: data.user.email, + picture: data.user.picture || 'https://via.placeholder.com/150', + }; + localStorage.setItem('user', JSON.stringify(loggedInUser)); + navigate('/dashboard'); + } catch (error) { + console.error('Ошибка верификации токена:', error); + } + }; + + const handleGoogleLoginError = (error: any) => { + console.error('Ошибка аутентификации через Google:', error); + }; + + return ( +
+
+

+ Sign In +

+
+
+ + +
+
+ + +
+ +
+
+ OR +
+
+ +
+

+ Don't have an account?{' '} + + Sign Up + +

+
+
+ ); +}; + +const LoginForm: React.FC = () => { + return ( + + + + ); +}; + +export default LoginForm; diff --git a/frontend/src/Components/Navigation.tsx b/frontend/src/Components/Navigation.tsx index c92e515..ddc20f2 100644 --- a/frontend/src/Components/Navigation.tsx +++ b/frontend/src/Components/Navigation.tsx @@ -5,15 +5,16 @@ import { Link } from 'react-router-dom'; import { MdOutlineDarkMode } from "react-icons/md"; import { CiLight } from "react-icons/ci"; import IconButton from '@mui/material/IconButton'; +import Avatar from '@mui/material/Avatar'; +import { CgLogIn } from "react-icons/cg"; import BackImage from '../assets/smallheadicon.png' export interface NavigationItem { icon: React.ReactNode, - title: string + title: string, link: string } - const NavigationItems: NavigationItem[] = [ { title: 'Dashboard', @@ -27,15 +28,11 @@ const NavigationItems: NavigationItem[] = [ } ] - interface NavigationProps { isExpanded: boolean, - } - const Navigation = ({ isExpanded = false }: NavigationProps) => { - const [theme, setTheme] = useState<'dark' | 'light'>('light') useEffect(() => { @@ -58,36 +55,47 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => { setTheme(theme === "dark" ? "light" : "dark") } + // Загружаем данные пользователя из localStorage (если имеются) + const [user, setUser] = useState(null); + useEffect(() => { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + setUser(JSON.parse(storedUser)); + } + }, []); + return (
- - + + - {isExpanded &&

Health AI

} + {isExpanded && ( +

+ Health AI +

+ )}
- {NavigationItems.map((item) => ( - + {item.icon} {isExpanded && item.title} @@ -95,27 +103,46 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => { ))}
- - + {/* Блок с иконкой пользователя и переключателем темы */} +
+ + + {user ? ( + + ) : ( + + )} + + + +
) } -export default Navigation \ No newline at end of file +export default Navigation; diff --git a/frontend/src/Components/RegistrationForm.tsx b/frontend/src/Components/RegistrationForm.tsx new file mode 100644 index 0000000..a020f9c --- /dev/null +++ b/frontend/src/Components/RegistrationForm.tsx @@ -0,0 +1,243 @@ +import React from 'react'; +import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google'; +import { Link, useNavigate } from 'react-router-dom'; +import gsap from 'gsap'; + +const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"; + +const RegistrationFormContent: React.FC = () => { + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Приведение типов для получения значений инпутов + const nameElement = document.getElementById('name') as HTMLInputElement | null; + const emailElement = document.getElementById('email') as HTMLInputElement | null; + const passwordElement = document.getElementById('password') as HTMLInputElement | null; + const confirmPasswordElement = document.getElementById('confirm-password') as HTMLInputElement | null; + + if (!nameElement || !emailElement || !passwordElement || !confirmPasswordElement) { + console.error('One or more input fields are missing'); + return; + } + + const name = nameElement.value; + const email = emailElement.value; + const password = passwordElement.value; + const confirmPassword = confirmPasswordElement.value; + + // Проверка совпадения паролей + if (password !== confirmPassword) { + console.error('Passwords do not match'); + alert('Passwords do not match'); + return; + } + + try { + const response = await fetch('http://localhost:5000/api/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, email, password }), + }); + + const data = await response.json(); + + if (response.ok) { + console.log('User registered successfully:', data.message); + // Создаем объект пользователя для авторизации (placeholder для аватара) + const loggedInUser = { + name, + email, + picture: 'https://via.placeholder.com/150', + }; + localStorage.setItem('user', JSON.stringify(loggedInUser)); + navigate('/dashboard'); + } else { + console.error('Error:', data.error); + alert(data.error); // Показываем сообщение об ошибке, например "User already exists" + } + } catch (error) { + console.error('Error registering user:', error); + alert('Error registering user'); + } + }; + + const handleGoogleLoginSuccess = async (response: any) => { + try { + const res = await fetch('http://localhost:5000/api/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: response.credential }), + }); + const data = await res.json(); + const loggedInUser = { + name: data.user.name, + email: data.user.email, + picture: data.user.picture || 'https://via.placeholder.com/150', + }; + localStorage.setItem('user', JSON.stringify(loggedInUser)); + navigate('/dashboard'); + } catch (error) { + console.error('Ошибка верификации токена:', error); + } + }; + + const handleGoogleLoginError = (error: any) => { + console.error('Ошибка аутентификации:', error); + }; + + return ( +
+
+

+ Create Your Account +

+

+ Join us to explore personalized health solutions. +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ OR +
+
+ +
+

+ Already have an account?{' '} + + Sign In + +

+
+
+ ); +}; + +const RegistrationForm: React.FC = () => { + return ( + + + + ); +}; + +export default RegistrationForm; diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 7329092..e44760a 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,13 +1,17 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { CgLogIn } from "react-icons/cg"; -import { Link } from 'react-router-dom'; -import BackImage from '../assets/smallheadicon.png' -import MultiStepForm from '../Components/MultistepForm'; +import BackImage from '../assets/smallheadicon.png'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; -import { Box } from '@mui/material'; +import { Box, Button, Avatar, Modal, Typography } from '@mui/material'; import gsap from 'gsap'; import { useGSAP } from '@gsap/react'; +import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google'; +import { Link, useNavigate } from 'react-router-dom'; +import RegistrationForm from "../Components/RegistrationForm"; +const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"; + +// Компонент для анимации стрелки вниз const BouncingArrow = () => { return ( { justifyContent: 'center', alignItems: 'center', mt: 2, - animation: 'bounce 1s infinite', // Додаємо анімацію - '@keyframes bounce': { // Описуємо ключові кадри для анімації + animation: 'bounce 1s infinite', + '@keyframes bounce': { '0%, 100%': { transform: 'translateY(0)', }, @@ -31,91 +35,157 @@ const BouncingArrow = () => { ); }; -const Navbar: React.FC = () => { + +interface NavbarProps { + user: any; + setUser: (user: any) => void; +} + +const Navbar: React.FC = ({ user, setUser }) => { + const navigate = useNavigate(); + + const handleSignOut = () => { + setUser(null); + localStorage.removeItem('user'); + }; + return ( ); }; const Home: React.FC = () => { + const navigate = useNavigate(); + const [user, setUser] = useState(null); + + // При загрузке страницы пытаемся загрузить данные пользователя из localStorage + useEffect(() => { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + setUser(JSON.parse(storedUser)); + } + }, []); + + // Анимация GSAP для элементов страницы useGSAP(() => { - gsap.from('#mainheading', { opacity: 0.3, ease: 'power2.inOut', duration: 0.5 }) - gsap.from('#secondheading', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.3, duration: 0.5 }) - gsap.from('#button', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.5, duration: 0.5 }) - gsap.from('#features', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.7, duration: 0.5 }) - gsap.from('#arrow', { opacity: 0, ease: 'power2.inOut', delay: 2, duration: 0.2 }) - gsap.to('#button-wrapper', { opacity: 1, ease: 'power2.inOut', delay: 2.5, duration: 0.5 }); - }, []) + gsap.from('#mainheading', { opacity: 0.3, ease: 'power2.inOut', duration: 0.5 }); + gsap.from('#secondheading', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.3, duration: 0.5 }); + gsap.from('#button', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.5, duration: 0.5 }); + gsap.from('#features', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.7, duration: 0.5 }); + gsap.from('#arrow', { opacity: 0, ease: 'power2.inOut', delay: 2, duration: 0.2 }); + gsap.to('#button', { opacity: 1, ease: 'power2.inOut', delay: 2.5, duration: 0.5 }); + }, []); + + // Обработчик нажатия на кнопку "Get started" + const handleGetStartedClick = () => { + if (!user) { + // Если пользователь не авторизован — переходим на страницу регистрации + navigate('/register'); + } else { + // Если авторизован — переходим на страницу dashboard + navigate('/dashboard'); + } + }; return ( -
+
- +
-

+

AI Assistant for Your Health

-

+

A solution for personalized health support, including tailored medication guidance and wellness insights. Take care of yourself with the power of modern technology.

-

Personalized Prescription

-

Receive tailored medication recommendations specifically - designed for your needs.

+

+ Receive tailored medication recommendations specifically designed for your needs. +

Health Monitoring

-

Stay informed about your health with real-time monitoring and - AI-driven insights.

+

+ Stay informed about your health with real-time monitoring and AI-driven insights. +

Advanced AI Support

-

Utilize AI support to ensure you're following the best routines - for a healthier lifestyle.

+

+ Utilize AI support to ensure you're following the best routines for a healthier lifestyle. +

-
-

Try it out

- +
+

Try it out

+
-
- - - +
+
- - {/*
*/} - {/* */} - {/*
*/} @@ -124,5 +194,3 @@ const Home: React.FC = () => { }; export default Home; - -