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 import Flask, request, jsonify
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
from google.oauth2 import id_token
|
||||||
|
from google.auth.transport import requests
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Импортируем функцию обработки из model.py
|
# Импортируем функцию обработки из model.py
|
||||||
from model import process_query_with_mistral
|
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)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Создаем Flask приложение
|
# Создаем Flask приложение
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app) # Разрешаем CORS для всех доменов
|
|
||||||
CORS(app, resources={r"/api/*": {"origins": "http://localhost:5173"}})
|
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'])
|
@app.route('/api/chat', methods=['POST'])
|
||||||
def chat():
|
def chat():
|
||||||
data = request.get_json()
|
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/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@gsap/react": "^2.1.1",
|
"@gsap/react": "^2.1.1",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@mui/icons-material": "^6.4.3",
|
||||||
"@mui/icons-material": "^6.1.5",
|
|
||||||
"@mui/material": "^6.1.5",
|
"@mui/material": "^6.1.5",
|
||||||
|
"@react-oauth/google": "^0.12.1",
|
||||||
"@reduxjs/toolkit": "^2.3.0",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"appwrite": "^16.0.2",
|
"appwrite": "^16.0.2",
|
||||||
"final-form": "^4.20.10",
|
"final-form": "^4.20.10",
|
||||||
@ -379,14 +379,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/cache": {
|
"node_modules/@emotion/cache": {
|
||||||
"version": "11.13.1",
|
"version": "11.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||||
"integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
|
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/memoize": "^0.9.0",
|
"@emotion/memoize": "^0.9.0",
|
||||||
"@emotion/sheet": "^1.4.0",
|
"@emotion/sheet": "^1.4.0",
|
||||||
"@emotion/utils": "^1.4.0",
|
"@emotion/utils": "^1.4.2",
|
||||||
"@emotion/weak-memoize": "^0.4.0",
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
"stylis": "4.2.0"
|
"stylis": "4.2.0"
|
||||||
}
|
}
|
||||||
@ -437,15 +436,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/serialize": {
|
"node_modules/@emotion/serialize": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
"integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/hash": "^0.9.2",
|
"@emotion/hash": "^0.9.2",
|
||||||
"@emotion/memoize": "^0.9.0",
|
"@emotion/memoize": "^0.9.0",
|
||||||
"@emotion/unitless": "^0.10.0",
|
"@emotion/unitless": "^0.10.0",
|
||||||
"@emotion/utils": "^1.4.1",
|
"@emotion/utils": "^1.4.2",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -494,10 +492,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/utils": {
|
"node_modules/@emotion/utils": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||||
"integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==",
|
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/weak-memoize": {
|
"node_modules/@emotion/weak-memoize": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
@ -1158,45 +1155,21 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz",
|
||||||
"integrity": "sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==",
|
"integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==",
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/icons-material": {
|
"node_modules/@mui/icons-material": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.3.tgz",
|
||||||
"integrity": "sha512-SbxFtO5I4cXfvhjAMgGib/t2lQUzcEzcDFYiRHRufZUeMMeXuoKaGsptfwAHTepYkv0VqcCwvxtvtWbpZLAbjQ==",
|
"integrity": "sha512-3IY9LpjkwIJVgL/SkZQKKCUcumdHdQEsJaIavvsQze2QEztBt0HJ17naToN0DBBdhKdtwX5xXrfD6ZFUeWWk8g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7"
|
"@babel/runtime": "^7.26.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@ -1206,7 +1179,7 @@
|
|||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mui/material": "^6.1.5",
|
"@mui/material": "^6.4.3",
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"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": {
|
"node_modules/@mui/material": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz",
|
||||||
"integrity": "sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==",
|
"integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/core-downloads-tracker": "^6.1.5",
|
"@mui/core-downloads-tracker": "^6.4.3",
|
||||||
"@mui/system": "^6.1.5",
|
"@mui/system": "^6.4.3",
|
||||||
"@mui/types": "^7.2.18",
|
"@mui/types": "^7.2.21",
|
||||||
"@mui/utils": "^6.1.5",
|
"@mui/utils": "^6.4.3",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@types/react-transition-group": "^4.4.11",
|
"@types/react-transition-group": "^4.4.12",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.3.1",
|
"react-is": "^19.0.0",
|
||||||
"react-transition-group": "^4.4.5"
|
"react-transition-group": "^4.4.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1245,7 +1217,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.5.0",
|
"@emotion/react": "^11.5.0",
|
||||||
"@emotion/styled": "^11.3.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",
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"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"
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
@ -1266,13 +1238,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/private-theming": {
|
"node_modules/@mui/private-theming": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz",
|
||||||
"integrity": "sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==",
|
"integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/utils": "^6.1.5",
|
"@mui/utils": "^6.4.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1293,14 +1264,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/styled-engine": {
|
"node_modules/@mui/styled-engine": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz",
|
||||||
"integrity": "sha512-tiyWzMkHeWlOoE6AqomWvYvdml8Nv5k5T+LDwOiwHEawx8P9Lyja6ZwWPU6xljwPXYYPT2KBp1XvMly7dsK46A==",
|
"integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@emotion/cache": "^11.13.1",
|
"@emotion/cache": "^11.13.5",
|
||||||
"@emotion/serialize": "^1.3.2",
|
"@emotion/serialize": "^1.3.3",
|
||||||
"@emotion/sheet": "^1.4.0",
|
"@emotion/sheet": "^1.4.0",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
@ -1327,16 +1297,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/system": {
|
"node_modules/@mui/system": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz",
|
||||||
"integrity": "sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==",
|
"integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/private-theming": "^6.1.5",
|
"@mui/private-theming": "^6.4.3",
|
||||||
"@mui/styled-engine": "^6.1.5",
|
"@mui/styled-engine": "^6.4.3",
|
||||||
"@mui/types": "^7.2.18",
|
"@mui/types": "^7.2.21",
|
||||||
"@mui/utils": "^6.1.5",
|
"@mui/utils": "^6.4.3",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
@ -1367,10 +1336,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.2.18",
|
"version": "7.2.21",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
|
||||||
"integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==",
|
"integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
@ -1381,17 +1349,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/utils": {
|
"node_modules/@mui/utils": {
|
||||||
"version": "6.1.5",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz",
|
||||||
"integrity": "sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==",
|
"integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.7",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/types": "^7.2.18",
|
"@mui/types": "^7.2.21",
|
||||||
"@types/prop-types": "^15.7.13",
|
"@types/prop-types": "^15.7.14",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.3.1"
|
"react-is": "^19.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@ -1469,6 +1436,15 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"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": {
|
"node_modules/@reduxjs/toolkit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz",
|
||||||
@ -1792,10 +1768,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.13",
|
"version": "15.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.12",
|
"version": "18.3.12",
|
||||||
@ -1818,11 +1793,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.11",
|
"version": "4.4.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
|
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||||
"license": "MIT",
|
"peerDependencies": {
|
||||||
"dependencies": {
|
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2433,7 +2407,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
@ -4099,10 +4072,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
|
||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
"integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.1.2",
|
"version": "9.1.2",
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@gsap/react": "^2.1.1",
|
"@gsap/react": "^2.1.1",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@mui/icons-material": "^6.4.3",
|
||||||
"@mui/icons-material": "^6.1.5",
|
|
||||||
"@mui/material": "^6.1.5",
|
"@mui/material": "^6.1.5",
|
||||||
|
"@react-oauth/google": "^0.12.1",
|
||||||
"@reduxjs/toolkit": "^2.3.0",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"appwrite": "^16.0.2",
|
"appwrite": "^16.0.2",
|
||||||
"final-form": "^4.20.10",
|
"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 Navigation from './Components/Navigation';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import LandingPage from './pages/LandingPage';
|
import LandingPage from './pages/LandingPage';
|
||||||
|
import RegistrationForm from "./Components/RegistrationForm.tsx";
|
||||||
|
import LoginForm from "./Components/LoginForm.tsx";
|
||||||
|
|
||||||
|
|
||||||
const Layout = () => (
|
const Layout = () => (
|
||||||
@ -21,6 +23,8 @@ function App() {
|
|||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<LandingPage />} />
|
<Route path='/' element={<LandingPage />} />
|
||||||
|
<Route path="/register" element={<RegistrationForm />} />
|
||||||
|
<Route path="/login" element={<LoginForm />} />
|
||||||
<Route path="solutions" element={<>Sorry not implemented yet</>} />
|
<Route path="solutions" element={<>Sorry not implemented yet</>} />
|
||||||
<Route path="contact" element={<>Sorry not implemented yet</>} />
|
<Route path="contact" element={<>Sorry not implemented yet</>} />
|
||||||
<Route path="about" 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 { MdOutlineDarkMode } from "react-icons/md";
|
||||||
import { CiLight } from "react-icons/ci";
|
import { CiLight } from "react-icons/ci";
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import { CgLogIn } from "react-icons/cg";
|
||||||
import BackImage from '../assets/smallheadicon.png'
|
import BackImage from '../assets/smallheadicon.png'
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
icon: React.ReactNode,
|
icon: React.ReactNode,
|
||||||
title: string
|
title: string,
|
||||||
link: string
|
link: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const NavigationItems: NavigationItem[] = [
|
const NavigationItems: NavigationItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
@ -27,15 +28,11 @@ const NavigationItems: NavigationItem[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
interface NavigationProps {
|
interface NavigationProps {
|
||||||
isExpanded: boolean,
|
isExpanded: boolean,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
||||||
|
|
||||||
const [theme, setTheme] = useState<'dark' | 'light'>('light')
|
const [theme, setTheme] = useState<'dark' | 'light'>('light')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -58,24 +55,34 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
|||||||
setTheme(theme === "dark" ? "light" : "dark")
|
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 (
|
return (
|
||||||
<div className='h-full p-3 w-fit'>
|
<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='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'>
|
<div className='flex flex-col items-start gap-12'>
|
||||||
<Link to='/' className='w-full flex items-center justify-center'>
|
<Link to='/' className='w-full flex items-center justify-center'>
|
||||||
<IconButton sx={{
|
<IconButton sx={{ width: 40, height: 40 }}>
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
}} >
|
|
||||||
<img src={BackImage} width={25} alt="" />
|
<img src={BackImage} width={25} alt="" />
|
||||||
</IconButton>
|
</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>
|
</Link>
|
||||||
<div className='flex flex-col p-1 gap-5 items-center'>
|
<div className='flex flex-col p-1 gap-5 items-center'>
|
||||||
|
|
||||||
{NavigationItems.map((item) => (
|
{NavigationItems.map((item) => (
|
||||||
<Link key={item.link} to={item.link} className='flex gap-2 items-center w-full'>
|
<Link key={item.link} to={item.link} className='flex gap-2 items-center w-full'>
|
||||||
<IconButton sx={{
|
<IconButton
|
||||||
|
sx={{
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
@ -87,7 +94,8 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
|||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}} >
|
}}
|
||||||
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{isExpanded && item.title}
|
{isExpanded && item.title}
|
||||||
@ -95,6 +103,24 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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'>
|
<button onClick={handleThemeSwitch} className='flex items-center gap-2'>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
@ -103,19 +129,20 @@ const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
background: theme === 'dark' ? 'white' : 'initial',
|
background: theme === 'dark' ? 'white' : 'initial',
|
||||||
'&:focus-visible': {
|
'&:focus-visible': {
|
||||||
outline: '2px solid blue', // Кастомний стиль фокуса
|
outline: '2px solid blue', // Кастомный стиль фокуса
|
||||||
outlineOffset: '0px', // Щоб межі виділення були близько до кнопки
|
outlineOffset: '0px',
|
||||||
borderRadius: '4px', // Залишає квадратні кути навколо фокуса
|
borderRadius: '4px',
|
||||||
},
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}
|
{theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}
|
{isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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 { CgLogIn } from "react-icons/cg";
|
||||||
import { Link } from 'react-router-dom';
|
import BackImage from '../assets/smallheadicon.png';
|
||||||
import BackImage from '../assets/smallheadicon.png'
|
|
||||||
import MultiStepForm from '../Components/MultistepForm';
|
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
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 gsap from 'gsap';
|
||||||
import { useGSAP } from '@gsap/react';
|
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 = () => {
|
const BouncingArrow = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -16,8 +20,8 @@ const BouncingArrow = () => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
mt: 2,
|
mt: 2,
|
||||||
animation: 'bounce 1s infinite', // Додаємо анімацію
|
animation: 'bounce 1s infinite',
|
||||||
'@keyframes bounce': { // Описуємо ключові кадри для анімації
|
'@keyframes bounce': {
|
||||||
'0%, 100%': {
|
'0%, 100%': {
|
||||||
transform: 'translateY(0)',
|
transform: 'translateY(0)',
|
||||||
},
|
},
|
||||||
@ -31,91 +35,157 @@ const BouncingArrow = () => {
|
|||||||
</Box>
|
</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 (
|
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">
|
<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">
|
<div className="text-2xl font-semibold text-dark-blue flex items-center">
|
||||||
Health AI
|
Health AI
|
||||||
<img src={BackImage} width={25} alt="" />
|
<img src={BackImage} width={25} alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex space-x-6 text-gray-600">
|
<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>
|
||||||
<li><Link to="/about" className="hover:text-bright-blue transition duration-300">About</Link></li>
|
<Link to="/dashboard" className="hover:text-bright-blue transition duration-300">
|
||||||
<li><Link to="/contact" className="hover:text-bright-blue transition duration-300">Contact</Link></li>
|
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>
|
</ul>
|
||||||
<div className='flex gap-2 items-center'>
|
<div className="flex items-center">
|
||||||
Sign in <CgLogIn size={25} />
|
{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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
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(() => {
|
useGSAP(() => {
|
||||||
gsap.from('#mainheading', { opacity: 0.3, ease: 'power2.inOut', 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('#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('#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('#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.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.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 (
|
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">
|
<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">
|
<div className="pt-20 flex flex-col items-center">
|
||||||
<h1 id='mainheading'
|
<h1
|
||||||
className="text-4xl flex items-center sm:text-5xl md:text-6xl font-semibold mb-4 text-center text-dark-blue">
|
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
|
AI Assistant for Your Health
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p id='secondheading'
|
<p
|
||||||
className="text-base sm:text-lg md:text-xl text-center max-w-2xl mb-8 text-gray-700">
|
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
|
A solution for personalized health support, including tailored medication guidance and wellness
|
||||||
insights. Take care of yourself with the power of modern technology.
|
insights. Take care of yourself with the power of modern technology.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-6 mb-10" id="features">
|
<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">
|
<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>
|
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
|
||||||
<p className="text-gray-600">Receive tailored medication recommendations specifically
|
<p className="text-gray-600">
|
||||||
designed for your needs.</p>
|
Receive tailored medication recommendations specifically designed for your needs.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
<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>
|
<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
|
<p className="text-gray-600">
|
||||||
AI-driven insights.</p>
|
Stay informed about your health with real-time monitoring and AI-driven insights.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
<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>
|
<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
|
<p className="text-gray-600">
|
||||||
for a healthier lifestyle.</p>
|
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='arrow' className='flex flex-col items-center mt-10 z-0'>
|
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
|
||||||
<p className='text-gray-600'>Try it out</p>
|
<p className="text-gray-600">Try it out</p>
|
||||||
<BouncingArrow />
|
<BouncingArrow />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="button-wrapper" className="flex justify-center opacity-0 mt-6">
|
<div id="button-wrapper" className="flex justify-center mt-6">
|
||||||
<Link id='button' to='/dashboard'>
|
|
||||||
<button
|
<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
|
Get started
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<footer className="mt-auto text-center text-gray-500 p-4">
|
||||||
<p>© {new Date().getFullYear()} Health AI. All rights reserved.</p>
|
<p>© {new Date().getFullYear()} Health AI. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
@ -124,5 +194,3 @@ const Home: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user