add register/login/database on aws
This commit is contained in:
parent
b16524e94a
commit
771a6f8432
Binary file not shown.
@ -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()
|
||||
|
188
frontend/package-lock.json
generated
188
frontend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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</>} />
|
||||
|
190
frontend/src/Components/LoginForm.tsx
Normal file
190
frontend/src/Components/LoginForm.tsx
Normal 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'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;
|
@ -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,
|
||||
}} >
|
||||
<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;
|
||||
|
243
frontend/src/Components/RegistrationForm.tsx
Normal file
243
frontend/src/Components/RegistrationForm.tsx
Normal 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;
|
@ -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 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>
|
||||
<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>© {new Date().getFullYear()} Health AI. All rights reserved.</p>
|
||||
</footer>
|
||||
@ -124,5 +194,3 @@ const Home: React.FC = () => {
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user