Disable initial email verification.

This commit is contained in:
2025-07-06 18:16:00 +02:00
parent eaced3e4e0
commit c1acc3122d
7 changed files with 183 additions and 25 deletions

27
app.py
View File

@@ -397,13 +397,18 @@ def load_logged_in_user():
else:
g.company = None
# Check if user is verified
if not g.user.is_verified and request.endpoint not in ['verify_email', 'static', 'logout', 'setup_company']:
# Allow unverified users to access only verification and static resources
if request.endpoint not in ['login', 'register']:
flash('Please verify your email address before accessing this page.', 'warning')
session.clear()
return redirect(url_for('login'))
# Check if user has email but not verified
if g.user and not g.user.is_verified and g.user.email:
# Add a flag for templates to show email verification nag
g.show_email_verification_nag = True
else:
g.show_email_verification_nag = False
# Check if user has no email at all
if g.user and not g.user.email:
g.show_email_nag = True
else:
g.show_email_nag = False
else:
g.company = None
@@ -569,8 +574,6 @@ def register():
error = None
if not username:
error = 'Username is required'
elif not email:
error = 'Email is required'
elif not password:
error = 'Password is required'
elif password != confirm_password:
@@ -596,7 +599,7 @@ def register():
if company and not error:
if User.query.filter_by(username=username, company_id=company.id).first():
error = 'Username already exists in this company'
elif User.query.filter_by(email=email, company_id=company.id).first():
elif email and User.query.filter_by(email=email, company_id=company.id).first():
error = 'Email already registered in this company'
if error is None and company:
@@ -690,8 +693,6 @@ def register_freelancer():
error = None
if not username:
error = 'Username is required'
elif not email:
error = 'Email is required'
elif not password:
error = 'Password is required'
elif password != confirm_password:
@@ -708,7 +709,7 @@ def register_freelancer():
if not error:
if User.query.filter_by(username=username).first():
error = 'Username already exists'
elif User.query.filter_by(email=email).first():
elif email and User.query.filter_by(email=email).first():
error = 'Email already registered'
if error is None:

View File

@@ -129,7 +129,7 @@ class Project(db.Model):
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(120), nullable=False)
email = db.Column(db.String(120), nullable=True)
password_hash = db.Column(db.String(128))
created_at = db.Column(db.DateTime, default=datetime.utcnow)

View File

@@ -608,6 +608,96 @@ footer {
margin: 0 0.5rem;
}
/* Email Nag Banner */
.email-nag-banner {
background-color: #e3f2fd;
border-bottom: 2px solid #1976d2;
padding: 0.75rem 1rem;
position: relative;
animation: slideDown 0.3s ease-out;
}
.email-nag-banner.email-verify {
background-color: #fff3cd;
border-color: #ffc107;
}
.email-nag-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.email-nag-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.email-nag-text {
flex: 1;
color: #333;
font-size: 0.95rem;
}
.email-nag-dismiss {
position: absolute;
top: 0.5rem;
right: 1rem;
background: none;
border: none;
font-size: 1.5rem;
color: #666;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s;
}
.email-nag-dismiss:hover {
background-color: rgba(0,0,0,0.1);
color: #333;
}
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(-100%);
opacity: 0;
}
}
@media (max-width: 768px) {
.email-nag-content {
padding-right: 2rem;
}
.email-nag-text {
font-size: 0.875rem;
}
}
/* Full width footer when no user (no sidebar) */
body:not(.has-user) footer {
margin-left: 0;

View File

@@ -206,6 +206,31 @@
{% endif %}
{% endwith %}
<!-- Email Nag Screens -->
{% if g.show_email_nag %}
<div class="email-nag-banner">
<div class="email-nag-content">
<span class="email-nag-icon">📧</span>
<span class="email-nag-text">
<strong>Add your email address</strong> to enable account recovery and receive important notifications.
</span>
<a href="{{ url_for('profile') }}" class="btn btn-sm btn-primary">Add Email</a>
<button class="email-nag-dismiss" onclick="dismissEmailNag()" title="Dismiss for this session">×</button>
</div>
</div>
{% elif g.show_email_verification_nag %}
<div class="email-nag-banner email-verify">
<div class="email-nag-content">
<span class="email-nag-icon">✉️</span>
<span class="email-nag-text">
<strong>Please verify your email address</strong> to ensure you can recover your account if needed.
</span>
<a href="{{ url_for('profile') }}" class="btn btn-sm btn-warning">Verify Email</a>
<button class="email-nag-dismiss" onclick="dismissEmailNag()" title="Dismiss for this session">×</button>
</div>
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
@@ -229,6 +254,28 @@
<script src="{{ url_for('static', filename='js/password-strength.js') }}"></script>
{% if g.user %}
<script src="{{ url_for('static', filename='js/user-dropdown.js') }}"></script>
<script>
function dismissEmailNag() {
// Hide the banner with animation
const banner = document.querySelector('.email-nag-banner');
if (banner) {
banner.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => {
banner.style.display = 'none';
}, 300);
}
// Store in session storage to not show again this session
sessionStorage.setItem('emailNagDismissed', 'true');
}
// Check if already dismissed this session
if (sessionStorage.getItem('emailNagDismissed') === 'true') {
const banner = document.querySelector('.email-nag-banner');
if (banner) {
banner.style.display = 'none';
}
}
</script>
{% else %}
<script src="{{ url_for('static', filename='js/splash.js') }}"></script>
{% endif %}

View File

@@ -93,7 +93,16 @@
</div>
<div class="info-item">
<span class="info-label">Email</span>
<span class="info-value">{{ user.email }}</span>
<span class="info-value">
{% if user.email %}
{{ user.email }}
{% if not user.is_verified %}
<span class="badge badge-warning">Unverified</span>
{% endif %}
{% else %}
<span class="text-muted">Not provided</span>
{% endif %}
</span>
</div>
<div class="info-item">
<span class="info-label">Role</span>
@@ -120,10 +129,20 @@
<form method="POST" action="{{ url_for('profile') }}" class="profile-form">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" class="form-control" value="{{ user.email }}" required>
<small>This email address is used for account verification and notifications.</small>
<input type="email" id="email" name="email" class="form-control" value="{{ user.email or '' }}" placeholder="your@email.com">
<small>This email address is used for account recovery and notifications.</small>
</div>
<button type="submit" class="btn btn-primary">Update Email</button>
{% if user.email and not user.is_verified %}
<div class="alert alert-warning">
<i>⚠️</i> Your email address is not verified.
<a href="{{ url_for('profile') }}" class="btn btn-sm btn-warning">Send Verification Email</a>
</div>
{% elif not user.email %}
<div class="alert alert-info">
<i></i> Adding an email address enables account recovery and important notifications.
</div>
{% endif %}
<button type="submit" class="btn btn-primary">{% if user.email %}Update{% else %}Add{% endif %} Email</button>
</form>
</div>
@@ -132,7 +151,7 @@
<h3>Change Password</h3>
<form method="POST" action="{{ url_for('profile') }}" class="password-form">
<!-- Hidden email field to maintain current email -->
<input type="hidden" name="email" value="{{ user.email }}">
<input type="hidden" name="email" value="{{ user.email or '' }}">
<div class="form-group">
<label for="current_password">Current Password</label>

View File

@@ -47,9 +47,9 @@
<div class="form-group input-icon">
<i>📧</i>
<input type="email" id="email" name="email" class="form-control" placeholder="your@email.com" required>
<label for="email">Email Address</label>
<small class="form-text text-muted">We'll send a verification link to this address</small>
<input type="email" id="email" name="email" class="form-control" placeholder="your@email.com (optional)">
<label for="email">Email Address (Optional)</label>
<small class="form-text text-muted">Recommended for account recovery and notifications</small>
</div>
<div class="form-group input-icon">
@@ -80,7 +80,7 @@
</div>
<div class="verification-notice">
<p>📨 After registration, you'll need to verify your email address before you can log in.</p>
<p>💡 You can register without an email, but we recommend adding one later for account recovery.</p>
</div>
</form>
</div>

View File

@@ -47,8 +47,9 @@
<div class="form-group input-icon">
<i>📧</i>
<input type="email" id="email" name="email" class="form-control" placeholder="your@email.com" required>
<label for="email">Email Address</label>
<input type="email" id="email" name="email" class="form-control" placeholder="your@email.com (optional)">
<label for="email">Email Address (Optional)</label>
<small class="form-text text-muted">Recommended for account recovery</small>
</div>
<div class="form-group input-icon">