add register/login/database on aws

This commit is contained in:
oleh 2025-02-09 16:50:33 +01:00
parent b16524e94a
commit 771a6f8432
9 changed files with 836 additions and 207 deletions

View File

@ -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()

View File

@ -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",

View File

@ -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",

View File

@ -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() {
<Router>
<Routes>
<Route path='/' element={<LandingPage />} />
<Route path="/register" element={<RegistrationForm />} />
<Route path="/login" element={<LoginForm />} />
<Route path="solutions" element={<>Sorry not implemented yet</>} />
<Route path="contact" element={<>Sorry not implemented yet</>} />
<Route path="about" element={<>Sorry not implemented yet</>} />

View File

@ -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 (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="login-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Sign In
</h2>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Sign In
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Don&apos;t have an account?{' '}
<Link to="/register" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign Up
</Link>
</p>
</div>
</div>
);
};
const LoginForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<LoginFormContent />
</GoogleOAuthProvider>
);
};
export default LoginForm;

View File

@ -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,24 +55,34 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
setTheme(theme === "dark" ? "light" : "dark")
}
// Загружаем данные пользователя из localStorage (если имеются)
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<div className='h-full p-3 w-fit'>
<div className='h-full rounded-xl border flex flex-col px-1 justify-between py-2 items-center dark:bg-slate-300 shadow-xl'>
<div className='flex flex-col items-start gap-12'>
<Link to='/' className='w-full flex items-center justify-center' >
<IconButton sx={{
width: 40,
height: 40,
}} >
<Link to='/' className='w-full flex items-center justify-center'>
<IconButton sx={{ width: 40, height: 40 }}>
<img src={BackImage} width={25} alt="" />
</IconButton>
{isExpanded && <p className='text-2xl font-semibold text-dark-blue flex items-center' >Health AI</p>}
{isExpanded && (
<p className='text-2xl font-semibold text-dark-blue flex items-center'>
Health AI
</p>
)}
</Link>
<div className='flex flex-col p-1 gap-5 items-center'>
{NavigationItems.map((item) => (
<Link key={item.link} to={item.link} className='flex gap-2 items-center w-full'>
<IconButton sx={{
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
@ -87,7 +94,8 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
boxShadow: 'none',
},
}),
}} >
}}
>
{item.icon}
</IconButton>
{isExpanded && item.title}
@ -95,6 +103,24 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
))}
</div>
</div>
{/* Блок с иконкой пользователя и переключателем темы */}
<div className="flex flex-col items-center gap-2">
<Link to={user ? '/profile' : '/login'} className="flex items-center">
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
backgroundColor: '#FFFFFF',
}}
>
{user ? (
<Avatar alt={user.name} src={user.picture} />
) : (
<CgLogIn size={24} />
)}
</IconButton>
</Link>
<button onClick={handleThemeSwitch} className='flex items-center gap-2'>
<IconButton
sx={{
@ -103,19 +129,20 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
borderRadius: 2,
background: theme === 'dark' ? 'white' : 'initial',
'&:focus-visible': {
outline: '2px solid blue', // Кастомний стиль фокуса
outlineOffset: '0px', // Щоб межі виділення були близько до кнопки
borderRadius: '4px', // Залишає квадратні кути навколо фокуса
outline: '2px solid blue', // Кастомный стиль фокуса
outlineOffset: '0px',
borderRadius: '4px',
},
}}>
}}
>
{theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}
</IconButton>
{isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}
</button>
</div>
</div>
</div>
)
}
export default Navigation
export default Navigation;

View File

@ -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 (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="registration-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Create Your Account
</h2>
<p style={{ textAlign: 'center', color: '#555', marginBottom: '24px' }}>
Join us to explore personalized health solutions.
</p>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="name" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Name
</label>
<input
type="text"
id="name"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '24px' }}>
<label
htmlFor="confirm-password"
style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}
>
Confirm Password
</label>
<input
type="password"
id="confirm-password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Register
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Already have an account?{' '}
<Link to="/login" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign In
</Link>
</p>
</div>
</div>
);
};
const RegistrationForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<RegistrationFormContent />
</GoogleOAuthProvider>
);
};
export default RegistrationForm;

View File

@ -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 (
<Box
@ -16,8 +20,8 @@ const BouncingArrow = () => {
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 = () => {
</Box>
);
};
const Navbar: React.FC = () => {
interface NavbarProps {
user: any;
setUser: (user: any) => void;
}
const Navbar: React.FC<NavbarProps> = ({ user, setUser }) => {
const navigate = useNavigate();
const handleSignOut = () => {
setUser(null);
localStorage.removeItem('user');
};
return (
<nav className="w-full bg-white shadow-md py-4 px-2 sm:px-8 flex justify-between items-center fixed top-0 left-0 right-0 z-50">
<div className="text-2xl font-semibold text-dark-blue flex items-center">
Health AI
<img src={BackImage} width={25} alt="" />
<img src={BackImage} width={25} alt="Logo" />
</div>
<ul className="flex space-x-6 text-gray-600">
<li><Link to="/dashboard" className="hover:text-bright-blue transition duration-300">Home</Link></li>
<li><Link to="/about" className="hover:text-bright-blue transition duration-300">About</Link></li>
<li><Link to="/contact" className="hover:text-bright-blue transition duration-300">Contact</Link></li>
<li>
<Link to="/dashboard" className="hover:text-bright-blue transition duration-300">
Home
</Link>
</li>
<li>
<Link to="/about" className="hover:text-bright-blue transition duration-300">
About
</Link>
</li>
<li>
<Link to="/contact" className="hover:text-bright-blue transition duration-300">
Contact
</Link>
</li>
</ul>
<div className='flex gap-2 items-center'>
Sign in <CgLogIn size={25} />
<div className="flex items-center">
{user ? (
<div className="flex items-center gap-2">
<Avatar alt={user.name} src={user.picture} />
<Button variant="outlined" size="small" onClick={handleSignOut}>
Sign Out
</Button>
</div>
) : (
<Button
startIcon={<CgLogIn />}
variant="outlined"
onClick={() => navigate('/register')}
>
Sign in
</Button>
)}
</div>
</nav>
);
};
const Home: React.FC = () => {
const navigate = useNavigate();
const [user, setUser] = useState<any>(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 (
<div style={{backgroundColor: '#d0e7ff'}} className="min-h-screen">
<div style={{ backgroundColor: '#d0e7ff' }} className="min-h-screen">
<div className="h-screen flex flex-col items-center justify-center bg-gradient-to-b text-gray-800 p-4">
<Navbar/>
<Navbar user={user} setUser={setUser} />
<div className="pt-20 flex flex-col items-center">
<h1 id='mainheading'
className="text-4xl flex items-center sm:text-5xl md:text-6xl font-semibold mb-4 text-center text-dark-blue">
<h1
id="mainheading"
className="text-4xl flex items-center sm:text-5xl md:text-6xl font-semibold mb-4 text-center text-dark-blue"
>
AI Assistant for Your Health
</h1>
<p id='secondheading'
className="text-base sm:text-lg md:text-xl text-center max-w-2xl mb-8 text-gray-700">
<p
id="secondheading"
className="text-base sm:text-lg md:text-xl text-center max-w-2xl mb-8 text-gray-700"
>
A solution for personalized health support, including tailored medication guidance and wellness
insights. Take care of yourself with the power of modern technology.
</p>
<div className="flex flex-col sm:flex-row gap-6 mb-10" id="features">
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
<p className="text-gray-600">Receive tailored medication recommendations specifically
designed for your needs.</p>
<p className="text-gray-600">
Receive tailored medication recommendations specifically designed for your needs.
</p>
</div>
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
<h3 className="text-xl font-medium mb-3 text-dark-blue">Health Monitoring</h3>
<p className="text-gray-600">Stay informed about your health with real-time monitoring and
AI-driven insights.</p>
<p className="text-gray-600">
Stay informed about your health with real-time monitoring and AI-driven insights.
</p>
</div>
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
<h3 className="text-xl font-medium mb-3 text-dark-blue">Advanced AI Support</h3>
<p className="text-gray-600">Utilize AI support to ensure you're following the best routines
for a healthier lifestyle.</p>
<p className="text-gray-600">
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
</p>
</div>
</div>
<div id='arrow' className='flex flex-col items-center mt-10 z-0'>
<p className='text-gray-600'>Try it out</p>
<BouncingArrow/>
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
<p className="text-gray-600">Try it out</p>
<BouncingArrow />
</div>
<div id="button-wrapper" className="flex justify-center opacity-0 mt-6">
<Link id='button' to='/dashboard'>
<div id="button-wrapper" className="flex justify-center mt-6">
<button
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md">
id="button"
onClick={handleGetStartedClick}
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md"
>
Get started
</button>
</Link>
</div>
</div>
</div>
{/*<div className='w-full h-screen flex flex-col justify-center items-center'>*/}
{/* <MultiStepForm />*/}
{/*</div>*/}
<footer className="mt-auto text-center text-gray-500 p-4">
<p>&copy; {new Date().getFullYear()} Health AI. All rights reserved.</p>
</footer>
@ -124,5 +194,3 @@ const Home: React.FC = () => {
};
export default Home;