auth modals

This commit is contained in:
aneuhmanh 2025-04-18 17:03:53 +03:00
parent bf5aeb677f
commit c9e8e5386f
31 changed files with 4105 additions and 3639 deletions

6
app.py
View File

@ -947,7 +947,11 @@ def terms_of_use():
def publication_rules(): def publication_rules():
return render_template('publication_rules.html') return render_template('publication_rules.html')
@app.context_processor
def inject_forms():
return dict(form=RegistrationForm())
if __name__ == '__main__': if __name__ == '__main__':
with app.app_context(): with app.app_context():
db.create_all() db.create_all()
app.run(debug=False) app.run(debug=True)

36
auth.py
View File

@ -6,7 +6,7 @@ from utils import get_client_ip
from models import RegistrationForm, LoginForm, PasswordField, RecaptchaField, SubmitField from models import RegistrationForm, LoginForm, PasswordField, RecaptchaField, SubmitField
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from wtforms.validators import DataRequired, Length, EqualTo from wtforms.validators import DataRequired, Length, EqualTo
from config import Config
auth_bp = Blueprint('auth', __name__) auth_bp = Blueprint('auth', __name__)
bcrypt = Bcrypt() bcrypt = Bcrypt()
@ -21,15 +21,10 @@ def register():
if form.validate_on_submit(): if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
ip_address = get_client_ip() ip_address = get_client_ip()
existing_user = User.query.filter_by(ip_address=ip_address).first()
if existing_user:
return render_template('register.html', form=form)
username = form.username.data.lower() username = form.username.data.lower()
existing_user = User.query.filter_by(ip_address=ip_address).first()
user = User(username=username, encrypted_password=hashed_password, ip_address=ip_address) user = User(username=username, encrypted_password=hashed_password, ip_address=ip_address)
try: try:
@ -39,7 +34,11 @@ def register():
except IntegrityError: except IntegrityError:
db.session.rollback() db.session.rollback()
return render_template('register.html', form=form) if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return render_template('register-modal.html', form=form)
return render_template('register.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY)
@auth_bp.route('/login', methods=['GET', 'POST']) @auth_bp.route('/login', methods=['GET', 'POST'])
def login(): def login():
@ -47,18 +46,31 @@ def login():
if form.validate_on_submit(): if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first() user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data): if user and user.check_password(form.password.data):
login_user(user) login_user(user)
if user.ip_address is None: if user.ip_address is None:
ip_address = get_client_ip() ip_address = get_client_ip()
user.ip_address = ip_address user.ip_address = ip_address
db.session.commit() db.session.commit()
return redirect(url_for('profile', username=user.username)) return redirect(url_for('profile', username=user.username))
return render_template('login.html', form=form) if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return render_template('login-modal.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY)
return render_template('login.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY)
@auth_bp.route('/register-modal')
def register_modal():
form = RegistrationForm()
return render_template('register-modal.html', form=form)
@auth_bp.route('/login-modal')
def login_modal():
form = LoginForm()
return render_template('login-modal.html', form=form)
@auth_bp.route('/logout') @auth_bp.route('/logout')
def logout(): def logout():

View File

@ -244,17 +244,15 @@ class LoginForm(FlaskForm):
recaptcha = RecaptchaField() recaptcha = RecaptchaField()
submit = SubmitField('Login') submit = SubmitField('Login')
class RegistrationForm(FlaskForm): class RegistrationForm(FlaskForm):
username = StringField( username = StringField('Username',
'Username', validators=[DataRequired(), Length(3,20),
validators=[ Regexp('^[a-zA-Z0-9_]+$')])
DataRequired(), password = PasswordField('Password',
Length(min=3, max=20), validators=[DataRequired(), Length(min=6)])
Regexp(r'^[a-zA-Z0-9_]+$', message="Username can contain only letters, numbers, and underscores.") confirm_password = PasswordField('Confirm Password',
] validators=[DataRequired(), EqualTo('password')])
)
password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
recaptcha = RecaptchaField() recaptcha = RecaptchaField()
submit = SubmitField('Register') submit = SubmitField('Register')

Binary file not shown.

View File

@ -975,3 +975,221 @@ html, body {
} }
} }
} }
#modal-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(13, 12, 28, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal {
width: 312px;
height: 400px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
border-width: thin;
border-color: #3C3882;
border-style: solid;
padding: 30px 40px;
position: fixed;
background-color: #0D0C1C;
display: none;
z-index: 1000;
}
.modal.active {
display: block;
}
.form-inner-container {
width: 312px;
height: 400px;
gap: 10px;
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
}
.reg-form-title {
width: 100%;
text-align: center;
margin: 0;
font-family: 'Nunito', sans-serif;
font-weight: 500;
font-size: 16px;
text-transform: uppercase;
color: #8784C9;
padding: 10px 0;
}
.modal-register-input-container {
width: 100%;
height: 39px;
border-radius: 10px;
border: 1px solid #8784C9;
opacity: 0.6;
position: relative;
background-color: transparent;
}
.modal-register-text-input {
width: calc(100% - 31px);
height: 16px;
position: absolute;
top: 11px;
left: 15.5px;
border: none;
outline: none;
background: transparent;
color: #8784C9;
font-family: 'Nunito', sans-serif;
font-size: 14px;
}
.modal-text-input::placeholder {
color: #8784C9;
opacity: 0.6;
}
.password-input {
-webkit-text-security: disc;
}
.modal-register-button {
width: 100%;
height: 38px;
border-radius: 10px;
background-color: #8784C9;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.3s ease;
}
.modal-register-button:hover {
background-color: #3C3882;
}
.modal-register-button-text {
font-family: 'Nunito', sans-serif;
font-weight: 500;
font-size: 16px;
color: #3C3882;
text-align: center;
transition: background-color 0.3s ease;
}
.modal-register-button:hover .modal-register-button-text {
color: #8784C9;
}
.login-link-container {
width: 312px;
height: 16px;
margin-bottom: 10px;
}
.login-prompt {
display: block;
width: 100%;
height: 16px;
font-family: 'Nunito', sans-serif;
font-weight: 400;
font-size: 12px;
line-height: 30px;
letter-spacing: 0;
color: #8784C9;
}
.login-link {
text-decoration: underline solid 0px;
text-decoration-skip-ink: auto;
color: #8784C9;
}
.login-form-title {
width: 100%;
text-align: center;
margin: 0;
font-family: 'Nunito', sans-serif;
font-weight: 500;
font-size: 16px;
text-transform: uppercase;
color: #8784C9;
padding: 10px 0;
}
.modal-login-input-container {
width: 100%;
height: 39px;
border-radius: 10px;
border: 1px solid #8784C9;
opacity: 0.6;
position: relative;
background-color: transparent;
}
.modal-login-text-input {
width: calc(100% - 31px);
height: 16px;
position: absolute;
top: 11px;
left: 15.5px;
border: none;
outline: none;
background: transparent;
color: #8784C9;
font-family: 'Nunito', sans-serif;
font-size: 14px;
}
.modal-text-input::placeholder {
color: #8784C9;
opacity: 0.6;
}
.password-input {
-webkit-text-security: disc;
}
.modal-login-button {
width: 100%;
height: 38px;
border-radius: 10px;
background-color: #8784C9;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.3s ease;
}
.modal-login-button:hover {
background-color: #9d9ad4;
}
.modal-login-button-text {
font-family: 'Nunito', sans-serif;
font-weight: 500;
font-size: 16px;
color: #3C3882;
text-align: center;
}
.login-link-container {
width: 312px;
height: 16px;
margin-bottom: 10px;
}
.login-prompt {
display: block;
width: 100%;
height: 16px;
font-family: 'Nunito', sans-serif;
font-weight: 400;
font-size: 12px;
line-height: 30px;
letter-spacing: 0;
color: #8784C9;
}
.login-link {
text-decoration: underline solid 0px;
text-decoration-skip-ink: auto;
color: #8784C9;
}

23
static/favicon.svg Normal file
View File

@ -0,0 +1,23 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2_365)">
<path d="M32.9817 30.0844C38.9837 20.3425 32.56 14.1926 30.7762 12.702C30.3873 13.8164 30.2317 15.462 30.2317 15.462C25.2408 9.77372 16.2013 10.9507 16.2013 10.9507C15.3941 17.4137 17.9782 22.2645 17.9782 22.2645C17.9782 22.2645 16.1052 22.1668 14.8166 22.7903C17.9055 27.6744 24.0397 32.7986 32.9817 30.0844Z" fill="#9CCB3B"/>
<path d="M29.088 15.3974C29.3517 15.6008 29.7035 15.6655 30.0336 15.5612C30.4296 15.439 30.7081 15.0939 30.7427 14.6899C30.7402 14.6806 30.7791 14.3104 30.859 13.8142C33.2206 16.3673 35.9452 21.3051 31.7847 28.3506C23.4789 30.6779 18.3202 25.7344 15.9467 22.5C16.5575 22.4047 17.1478 22.3998 17.4096 22.4139C17.7793 22.4338 18.135 22.2576 18.3314 21.9447C18.5338 21.6352 18.5462 21.2422 18.3756 20.9186C18.3533 20.8747 16.1695 16.6649 16.6252 11.0314C18.8589 10.9166 25.2022 11.0136 28.9321 15.2547C28.9784 15.3122 29.0315 15.3577 29.088 15.3974ZM30.8952 11.0268C30.6435 10.8302 30.3071 10.7663 29.9907 10.847C29.6614 10.9361 29.4007 11.1814 29.2907 11.496C29.1841 11.8046 29.0912 12.1444 29.0137 12.4851C23.6144 8.09621 15.9077 9.02754 15.5501 9.06934C15.0864 9.12985 14.7172 9.49451 14.6578 9.95519C14.0821 14.627 15.1713 18.4636 15.9213 20.4387C15.2652 20.5164 14.5056 20.6769 13.842 21.0014C13.5787 21.1276 13.3898 21.3585 13.3063 21.6409C13.2262 21.9173 13.2684 22.2156 13.4219 22.4589C15.1095 25.1344 21.5041 33.6052 32.7613 30.1945C33.0033 30.119 33.2128 29.9625 33.3357 29.7493C39.629 19.544 33.0773 12.8593 30.9183 11.0556C30.9192 11.0403 30.9072 11.0336 30.8952 11.0268Z" fill="#1F1D44"/>
<path d="M29.5863 27.73C22.374 16.414 29.7945 9.09085 31.8591 7.31192C32.3351 8.61155 32.5471 10.5382 32.5471 10.5382C38.2961 3.78147 48.9154 5.00264 48.9154 5.00264C49.9782 12.5657 47.0359 18.2983 47.0359 18.2983C47.0359 18.2983 49.23 18.1509 50.7521 18.8592C47.2184 24.6397 40.1189 30.7552 29.5863 27.73Z" fill="#9CCB3B"/>
<path d="M33.8871 10.4423C33.5816 10.6854 33.1704 10.7674 32.7814 10.6509C32.315 10.5147 31.9822 10.115 31.9343 9.6419C31.9372 9.63095 31.8849 9.19759 31.7823 8.61725C29.0594 11.6521 25.954 17.4891 30.9586 25.6763C40.7385 28.259 46.6975 22.3724 49.4221 18.5387C48.7043 18.4377 48.0121 18.4424 47.7054 18.4635C47.2724 18.4933 46.8522 18.293 46.6162 17.9295C46.3733 17.5702 46.3517 17.1096 46.5459 16.7273C46.5713 16.6755 49.0559 11.7014 48.4202 5.10466C45.7992 5.00925 38.364 5.23444 34.0673 10.2723C34.0141 10.3405 33.9527 10.3948 33.8871 10.4423ZM31.6896 5.34995C31.9813 5.11497 32.3744 5.03419 32.7469 5.12317C33.1346 5.22185 33.4446 5.50495 33.5793 5.87177C33.7098 6.2317 33.8248 6.62851 33.9218 7.02657C40.173 1.78611 49.2253 2.74263 49.6453 2.78536C50.19 2.84816 50.6294 3.26921 50.7073 3.80827C51.4664 9.27543 50.2585 13.7927 49.4148 16.1216C50.1854 16.2011 51.0789 16.376 51.8627 16.7447C52.1737 16.888 52.3993 17.1555 52.5023 17.485C52.6012 17.8078 52.557 18.1582 52.3815 18.4461C50.4511 21.6126 43.1064 31.6562 29.8469 27.8553C29.5618 27.771 29.3134 27.5911 29.1654 27.3434C21.6033 15.4891 29.1644 7.53666 31.663 5.38401C31.6618 5.36619 31.6757 5.35807 31.6896 5.34995Z" fill="#1F1D44"/>
<path d="M34.1094 36.1705C34.1094 44.0866 27.6163 50.5007 19.6026 50.5007C11.5889 50.5007 5.0957 44.0866 5.0957 36.1705C5.0957 28.2544 11.5889 21.8403 19.6026 21.8403C27.6163 21.8403 34.1094 28.2544 34.1094 36.1705Z" fill="#5751B3"/>
<path d="M19.0931 22.4492C11.8109 22.4492 5.88486 28.3031 5.88486 35.4966C5.88486 42.6901 11.8109 48.544 19.0931 48.544C26.3753 48.544 32.3013 42.6901 32.3013 35.4966C32.3013 28.3031 26.3753 22.4492 19.0931 22.4492ZM19.0931 51.1096C10.3807 51.1096 3.2876 44.1109 3.2876 35.5047C3.2876 26.8985 10.3725 19.8998 19.0931 19.8998C27.8054 19.8998 34.8986 26.8985 34.8986 35.5047C34.8903 44.1109 27.8054 51.1096 19.0931 51.1096Z" fill="#1F1D44"/>
<path d="M15.0837 24.8519C15.7967 24.9052 16.3352 25.5154 16.2806 26.2279C16.2266 26.9322 15.7036 26.9264 14.8535 27.3185C12.931 28.2051 12.9009 28.5974 11.729 30.0161C11.2961 30.7166 11.0573 31.2524 10.336 31.1985C9.62302 31.1452 9.08515 30.5269 9.13911 29.8226C9.71701 28.1966 12.4524 25.307 15.0837 24.8519Z" fill="#8784C9"/>
<path d="M17.2603 25.0068C17.2603 25.3082 17.3815 25.5974 17.5973 25.8105C17.8131 26.0237 18.1058 26.1434 18.4109 26.1434C18.7161 26.1434 19.0088 26.0237 19.2246 25.8105C19.4404 25.5974 19.5616 25.3082 19.5616 25.0068C19.5616 24.7053 19.4404 24.4162 19.2246 24.203C19.0088 23.9899 18.7161 23.8701 18.4109 23.8701C18.1058 23.8701 17.8131 23.9899 17.5973 24.203C17.3815 24.4162 17.2603 24.7053 17.2603 25.0068Z" fill="#8784C9"/>
<path d="M58.4385 35.6022C58.4385 43.5183 51.9454 49.9323 43.9317 49.9323C35.918 49.9323 29.4248 43.5183 29.4248 35.6022C29.4248 27.6861 35.918 21.272 43.9317 21.272C51.9454 21.272 58.4385 27.6861 58.4385 35.6022Z" fill="#5751B3"/>
<path d="M40.152 25.0955C40.865 25.1488 41.4035 25.759 41.349 26.4714C41.295 27.1758 40.772 27.17 39.9219 27.562C37.9994 28.4487 37.9693 28.841 36.7973 30.2597C36.3645 30.9602 36.1256 31.496 35.4044 31.4421C34.6914 31.3888 34.1535 30.7705 34.2075 30.0661C34.7854 28.4402 37.5207 25.5505 40.152 25.0955Z" fill="#8784C9"/>
<path d="M43.3397 22.4492C36.0575 22.4492 30.1314 28.3031 30.1314 35.4966C30.1314 42.6901 36.0575 48.544 43.3397 48.544C50.6219 48.544 56.5479 42.6901 56.5479 35.4966C56.5479 28.3031 50.6219 22.4492 43.3397 22.4492ZM43.3397 51.1096C34.6273 51.1096 27.5342 44.1109 27.5342 35.5047C27.5342 26.8985 34.6191 19.8998 43.3397 19.8998C52.052 19.8998 59.1451 26.8985 59.1451 35.5047C59.1369 44.1109 52.052 51.1096 43.3397 51.1096Z" fill="#1F1D44"/>
<path d="M42.3286 25.2503C42.3286 25.5518 42.4498 25.8409 42.6656 26.0541C42.8814 26.2672 43.1741 26.387 43.4793 26.387C43.7845 26.387 44.0772 26.2672 44.293 26.0541C44.5088 25.8409 44.63 25.5518 44.63 25.2503C44.63 24.9489 44.5088 24.6598 44.293 24.4466C44.0772 24.2334 43.7845 24.1137 43.4793 24.1137C43.1741 24.1137 42.8814 24.2334 42.6656 24.4466C42.4498 24.6598 42.3286 24.9489 42.3286 25.2503Z" fill="#8784C9"/>
<path d="M43.4119 36.6171L41.7834 39.813M41.7834 39.813L40.1548 43.0092M41.7834 39.813L45.0188 41.4217M41.7834 39.813L38.5479 38.2042" stroke="#1F1D44" stroke-width="2" stroke-linecap="round"/>
<path d="M17.1107 36.6171L15.4821 39.813M15.4821 39.813L13.8535 43.0092M15.4821 39.813L18.7175 41.4217M15.4821 39.813L12.2466 38.2042" stroke="#1F1D44" stroke-width="2" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_2_365">
<rect width="60" height="60" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -5,23 +5,12 @@ document.addEventListener('DOMContentLoaded', () => {
const thumbnail = cardCover.querySelector('.thumbnail'); const thumbnail = cardCover.querySelector('.thumbnail');
const video = cardCover.querySelector('.preview-video'); const video = cardCover.querySelector('.preview-video');
const videoSource = video.querySelector('source'); const videoSource = video.querySelector('source');
let hoverTimeout;
cardCover.addEventListener('mouseenter', () => {
hoverTimeout = setTimeout(() => {
if (!videoSource.src) {
videoSource.src = video.dataset.src;
video.load();
}
const showPreview = () => {
thumbnail.style.display = 'none'; thumbnail.style.display = 'none';
video.style.display = 'block'; video.style.display = 'block';
video.addEventListener('loadedmetadata', () => {
if (video.duration > 15) { if (video.duration > 15) {
video.currentTime = 10; video.currentTime = 10;
const loopSegment = () => { const loopSegment = () => {
if (video.currentTime >= 15) { if (video.currentTime >= 15) {
@ -32,7 +21,6 @@ document.addEventListener('DOMContentLoaded', () => {
video.addEventListener('timeupdate', loopSegment); video.addEventListener('timeupdate', loopSegment);
cardCover.loopSegment = loopSegment; cardCover.loopSegment = loopSegment;
} else { } else {
video.currentTime = 0; video.currentTime = 0;
const loopEntireVideo = () => { const loopEntireVideo = () => {
if (video.currentTime >= video.duration) { if (video.currentTime >= video.duration) {
@ -45,13 +33,21 @@ document.addEventListener('DOMContentLoaded', () => {
} }
video.play(); video.play();
}); };
}, 2000);
cardCover.addEventListener('mouseenter', () => {
video.pause();
videoSource.src = video.dataset.src;
video.load();
video.addEventListener('canplay', showPreview, { once: true });
}); });
cardCover.addEventListener('mouseleave', () => { cardCover.addEventListener('mouseleave', (e) => {
clearTimeout(hoverTimeout); const toElement = e.relatedTarget;
if (!cardCover.contains(toElement)) {
video.pause(); video.pause();
video.currentTime = 0;
video.style.display = 'none'; video.style.display = 'none';
thumbnail.style.display = 'block'; thumbnail.style.display = 'block';
@ -59,6 +55,9 @@ document.addEventListener('DOMContentLoaded', () => {
video.removeEventListener('timeupdate', cardCover.loopSegment); video.removeEventListener('timeupdate', cardCover.loopSegment);
delete cardCover.loopSegment; delete cardCover.loopSegment;
} }
videoSource.src = '';
}
}); });
}); });
}); });

View File

@ -14,4 +14,79 @@ document.addEventListener('DOMContentLoaded', function () {
dropdownMenu.addEventListener('click', function (event) { dropdownMenu.addEventListener('click', function (event) {
event.stopPropagation(); event.stopPropagation();
}); });
// Делегируем клики для открытия логина/регистрации внутри любых модалок
document.addEventListener('click', function (e) {
const target = e.target.closest('[data-action]');
if (!target) return;
e.preventDefault();
const action = target.dataset.action;
if (action === 'open-register') {
openRegisterModal();
} else if (action === 'open-login') {
openLoginModal();
}
});
});
function closeAllModals() {
const modalContainer = document.getElementById('modal-container');
if (modalContainer) modalContainer.remove();
}
function openRegisterModal() {
closeAllModals();
const modalContainer = document.createElement('div');
modalContainer.id = 'modal-container';
document.body.appendChild(modalContainer);
fetch('/register-modal')
.then(response => response.text())
.then(html => {
modalContainer.innerHTML = html;
reloadRecaptcha(modalContainer);
});
}
function openLoginModal() {
closeAllModals();
const modalContainer = document.createElement('div');
modalContainer.id = 'modal-container';
document.body.appendChild(modalContainer);
fetch('/login-modal')
.then(response => response.text())
.then(html => {
modalContainer.innerHTML = html;
reloadRecaptcha(modalContainer);
});
}
function reloadRecaptcha(modalContainer) {
const recaptchaScript = modalContainer.querySelector('script[src*="recaptcha"]');
if (recaptchaScript) recaptchaScript.remove();
const script = document.createElement('script');
script.src = 'https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback';
document.head.appendChild(script);
}
window.recaptchaCallback = function () {
const recaptchaElement = document.querySelector('.g-recaptcha');
if (recaptchaElement && typeof grecaptcha !== 'undefined') {
grecaptcha.render(recaptchaElement, {
theme: "dark",
size: "default"
});
}
};
document.addEventListener('click', function (e) {
const modal = document.querySelector('#modal-container .modal');
if (modal && !modal.contains(e.target)) {
closeAllModals();
}
}); });

View File

@ -0,0 +1,47 @@
<div id="loginModal" class="modal active">
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="form-inner-container">
<p class="login-form-title">ВХОД</p>
<div class="modal-login-input-container">
{{ form.username(class_="modal-login-text-input", placeholder="Имя пользователя") }}
</div>
<div class="modal-login-input-container">
{{ form.password(class_="modal-login-text-input password-input", placeholder="Пароль") }}
</div>
<div class="login-link-container">
<span class="login-prompt">
<a href="#" class="login-link" data-action="forgot-password">Забыли пароль?</a>
</span>
</div>
<div class="login-link-container">
<span class="login-prompt">
Нет аккаунта?
<a href="#" class="login-link" data-action="open-register">Зарегистрируйся тут!</a>
</span>
</div>
<div class="recaptcha-wrapper">
{{ form.recaptcha() }}
</div>
<button type="submit" class="modal-login-button">
<span class="modal-login-button-text">Войти</span>
</button>
</div>
</form>
</div>
<script>
document.addEventListener('click', function(e) {
if (e.target.matches('[data-action="open-register"]')) {
e.preventDefault();
closeAllModals();
openRegisterModal();
}
});
</script>

View File

@ -1,35 +1,60 @@
{% extends "auth.html" %} <!DOCTYPE html>
{% block title %}🫐login - artberry🫐{% endblock %} <html lang="ru">
{% set action = 'auth.login' %} <head>
<meta charset="UTF-8">
{% block content %} <meta name="viewport" content="width=device-width, initial-scale=1.0">
<form method="POST" action="{{ url_for(action) }}"> <script src="https://www.google.com/recaptcha/api.js" async defer></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@500&display=swap" rel="stylesheet">
</head>
<body>
<div id="loginModal" class="modal active">
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
<div class="form-inner-container">
<p class="login-form-title">ВХОД</p>
{% for field, errors in form.errors.items() %} <div class="modal-login-input-container">
<ul> {{ form.username(class_="modal-login-text-input", placeholder="Имя пользователя") }}
{% for error in errors %}
<li><strong>{{ field.label }}:</strong> {{ error }}</li>
{% endfor %}
</ul>
{% endfor %}
<label for="{{ form.username.id }}">{{ form.username.label.text }}</label>
{{ form.username(class="input-field", placeholder="Enter username") }}<br>
<label for="{{ form.password.id }}">{{ form.password.label.text }}</label>
{{ form.password(class="input-field", placeholder="Enter password") }}<br>
<div class="recaptcha-container">
{{ form.recaptcha.label }}
{{ form.recaptcha() }}<br>
</div> </div>
{{ form.submit(class="login-button button", value="Login") }} <div class="modal-login-input-container">
</form> {{ form.password(class_="modal-login-text-input password-input", placeholder="Пароль") }}
</div>
<div class="link-container"> <div class="login-link-container">
<span class="link-text">Don't have an account?</span> <span class="login-prompt">
<a href="{{ url_for('auth.register') }}" class="button">Register</a> <a href="#" class="login-link" data-action="forgot-password">Забыли пароль?</a>
</div> </span>
{% endblock %} </div>
<div class="login-link-container">
<span class="login-prompt">
Нет аккаунта?
<a href="#" class="login-link" data-action="open-register">Зарегистрируйся тут!</a>
</span>
</div>
<div class="recaptcha-wrapper">
{{ form.recaptcha() }}
</div>
<button type="submit" class="modal-login-button">
<span class="modal-login-button-text">Войти</span>
</button>
</div>
</form>
</div>
<script>
document.addEventListener('click', function(e) {
if (e.target.matches('[data-action="open-register"]')) {
e.preventDefault();
closeAllModals();
openRegisterModal();
}
});
</script>
</body>
</html>

View File

@ -45,11 +45,15 @@
<img src="{{ url_for('static', filename='navbar/discord-hover.svg') }}" alt="Discord Hover" <img src="{{ url_for('static', filename='navbar/discord-hover.svg') }}" alt="Discord Hover"
class="discord-hover-icon"> class="discord-hover-icon">
</div> </div>
<a href="{{ url_for('auth.login') }}"><button class="login-btn">ВОЙТИ</button></a> <button class="login-btn" onclick="openRegisterModal()">ВОЙТИ</button>
</div> </div>
</div> </div>
</div> </div>
<script src="{{ url_for('static', filename='js/navbar.js') }}"></script> <script src="{{ url_for('static', filename='js/navbar.js') }}"></script>
<script>
window.recaptchaSiteKey = "{{ recaptcha_key }}";
</script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,35 @@
<div id="registerModal" class="modal active">
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }}
<div class="form-inner-container">
<p class="reg-form-title">РЕГИСТРАЦИЯ</p>
<div class="modal-register-input-container">
{{ form.username(class_="modal-register-text-input", placeholder="Имя пользователя") }}
</div>
<div class="modal-register-input-container">
{{ form.password(class_="modal-register-text-input password-input", placeholder="Пароль") }}
</div>
<div class="modal-register-input-container">
{{ form.confirm_password(class_="modal-register-text-input password-input", placeholder="Повтори пароль") }}
</div>
<div class="login-link-container">
<span class="login-prompt">
Уже есть аккаунт?
<a href="#" class="login-link" data-action="open-login">Войди тут!</a>
</span>
</div>
<div class="recaptcha-wrapper">
{{ form.recaptcha() }}
</div>
<button type="submit" class="modal-register-button">
<span class="modal-register-button-text">Зарегистрироваться</span>
</button>
</div>
</form>
</div>

View File

@ -1,28 +1,54 @@
{% extends "auth.html" %} <!DOCTYPE html>
{% block title %}🫐register - artberry🫐{% endblock %} <html lang="ru">
{% set action = 'auth.register' %} <head>
{% block content %} <meta charset="UTF-8">
<form method="POST" action="{{ url_for(action) }}"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@500&display=swap" rel="stylesheet">
</head>
<body>
<div id="registerModal" class="modal active">
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
<div class="form-inner-container">
<p class="reg-form-title">РЕГИСТРАЦИЯ</p>
<label for="{{ form.username.id }}">{{ form.username.label.text }}</label> <div class="modal-register-input-container">
{{ form.username(class="input-field", placeholder="Enter username") }}<br> {{ form.username(class_="modal-register-text-input", placeholder="Имя пользователя") }}
<label for="{{ form.password.id }}">{{ form.password.label.text }}</label>
{{ form.password(class="input-field", placeholder="Enter password") }}<br>
<label for="{{ form.confirm_password.id }}">{{ form.confirm_password.label.text }}</label>
{{ form.confirm_password(class="input-field", placeholder="Repeat password") }}<br>
<div class="recaptcha-container">
{{ form.recaptcha.label }}
{{ form.recaptcha() }}<br>
</div> </div>
{{ form.submit(class="login-button button", value="Register") }} <div class="modal-register-input-container">
</form> {{ form.password(class_="modal-register-text-input password-input", placeholder="Пароль") }}
<div class="link-container"> </div>
<span class="link-text">Already have an account?</span>
<a href="{{ url_for('auth.login') }}" class="button">Login</a> <div class="modal-register-input-container">
</div> {{ form.confirm_password(class_="modal-register-text-input password-input", placeholder="Повтори пароль") }}
{% endblock %} </div>
<div class="login-link-container">
<span class="login-prompt">
Уже есть аккаунт?
<a href="#" class="login-link" data-action="open-login">Войди тут!</a>
</span>
</div>
<div class="recaptcha-wrapper">
{{ form.recaptcha() }}
</div>
<button type="submit" class="modal-register-button">
<span class="modal-register-button-text">Зарегестрироваться</span>
</button>
</div>
</form>
</div>
</body>
<script>
function openLoginModal() {
document.getElementById('registerModal').classList.remove('active');
document.getElementById('loginModal').classList.add('active');
}
</script>
</html>

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<link rel="icon" href="{{ url_for('static', filename='favicon.svg') }}" type="image/svg+xml">
</head> </head>
<body> <body>
{% include 'navbar.html' %} {% include 'navbar.html' %}
@ -124,6 +125,5 @@
<button class="view-more-button"><span class="new-context-button-text">Смотреть Больше</span></button> <button class="view-more-button"><span class="new-context-button-text">Смотреть Больше</span></button>
</div> </div>
<script src="{{ url_for('static', filename='js/hoverPreview.js') }}"></script> <script src="{{ url_for('static', filename='js/hoverPreview.js') }}"></script>
<script src="{{ url_for('static', filename='js/adjustScrollbar.js') }}"></script>
</body> </body>
</html> </html>