Update About page and routing (when not logged in).
This commit is contained in:
159
app.py
159
app.py
@@ -57,10 +57,10 @@ db.init_app(app)
|
||||
def run_migrations():
|
||||
"""Run all database migrations and initialize system settings."""
|
||||
import sqlite3
|
||||
|
||||
|
||||
# Determine database path based on environment
|
||||
db_path = '/data/timetrack.db' if os.path.exists('/data') else 'timetrack.db'
|
||||
|
||||
|
||||
# Check if database exists
|
||||
if not os.path.exists(db_path):
|
||||
print("Database doesn't exist. Creating new database.")
|
||||
@@ -86,7 +86,7 @@ def run_migrations():
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
|
||||
# Migrate time_entry table
|
||||
cursor.execute("PRAGMA table_info(time_entry)")
|
||||
time_entry_columns = [column[1] for column in cursor.fetchall()]
|
||||
@@ -130,13 +130,13 @@ def run_migrations():
|
||||
# Check and add missing columns to work_config
|
||||
cursor.execute("PRAGMA table_info(work_config)")
|
||||
work_config_columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
|
||||
work_config_migrations = [
|
||||
('additional_break_minutes', "ALTER TABLE work_config ADD COLUMN additional_break_minutes INTEGER DEFAULT 15"),
|
||||
('additional_break_threshold_hours', "ALTER TABLE work_config ADD COLUMN additional_break_threshold_hours FLOAT DEFAULT 9.0"),
|
||||
('user_id', "ALTER TABLE work_config ADD COLUMN user_id INTEGER")
|
||||
]
|
||||
|
||||
|
||||
for column_name, sql_command in work_config_migrations:
|
||||
if column_name not in work_config_columns:
|
||||
print(f"Adding {column_name} column to work_config...")
|
||||
@@ -168,11 +168,11 @@ def run_migrations():
|
||||
# First ensure all users have roles set based on is_admin
|
||||
cursor.execute("UPDATE user SET role = 'Administrator' WHERE is_admin = 1 AND (role IS NULL OR role = '')")
|
||||
cursor.execute("UPDATE user SET role = 'Team Member' WHERE is_admin = 0 AND (role IS NULL OR role = '')")
|
||||
|
||||
|
||||
# Drop the is_admin column (SQLite requires table recreation)
|
||||
print("Removing is_admin column...")
|
||||
cursor.execute("PRAGMA foreign_keys=off")
|
||||
|
||||
|
||||
# Create new table without is_admin column
|
||||
cursor.execute("""
|
||||
CREATE TABLE user_new (
|
||||
@@ -191,18 +191,18 @@ def run_migrations():
|
||||
two_factor_secret VARCHAR(32)
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
# Copy data from old table to new table (excluding is_admin)
|
||||
cursor.execute("""
|
||||
INSERT INTO user_new (id, username, email, password_hash, created_at, is_verified,
|
||||
verification_token, token_expiry, is_blocked, role, team_id,
|
||||
INSERT INTO user_new (id, username, email, password_hash, created_at, is_verified,
|
||||
verification_token, token_expiry, is_blocked, role, team_id,
|
||||
two_factor_enabled, two_factor_secret)
|
||||
SELECT id, username, email, password_hash, created_at, is_verified,
|
||||
verification_token, token_expiry, is_blocked, role, team_id,
|
||||
SELECT id, username, email, password_hash, created_at, is_verified,
|
||||
verification_token, token_expiry, is_blocked, role, team_id,
|
||||
two_factor_enabled, two_factor_secret
|
||||
FROM user
|
||||
""")
|
||||
|
||||
|
||||
# Drop old table and rename new table
|
||||
cursor.execute("DROP TABLE user")
|
||||
cursor.execute("ALTER TABLE user_new RENAME TO user")
|
||||
@@ -278,7 +278,7 @@ def run_migrations():
|
||||
|
||||
# Commit all schema changes
|
||||
conn.commit()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during database migration: {e}")
|
||||
conn.rollback()
|
||||
@@ -288,60 +288,60 @@ def run_migrations():
|
||||
|
||||
# Now use SQLAlchemy for data migrations
|
||||
db.create_all() # This will create any remaining tables defined in models
|
||||
|
||||
|
||||
# Initialize system settings
|
||||
init_system_settings()
|
||||
|
||||
|
||||
# Handle company migration and admin user setup
|
||||
migrate_to_company_model()
|
||||
migrate_data()
|
||||
|
||||
|
||||
print("Database migrations completed successfully!")
|
||||
|
||||
def migrate_to_company_model():
|
||||
"""Migrate existing data to support company model"""
|
||||
import sqlite3
|
||||
|
||||
|
||||
# Determine database path based on environment
|
||||
db_path = '/data/timetrack.db' if os.path.exists('/data') else 'timetrack.db'
|
||||
|
||||
|
||||
# Connect to the database for raw SQL operations
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
||||
try:
|
||||
# Check if company_id column exists in user table
|
||||
cursor.execute("PRAGMA table_info(user)")
|
||||
user_columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
|
||||
if 'company_id' not in user_columns:
|
||||
print("Migrating to company model...")
|
||||
|
||||
|
||||
# Add company_id columns to existing tables
|
||||
tables_to_migrate = [
|
||||
('user', 'ALTER TABLE user ADD COLUMN company_id INTEGER'),
|
||||
('team', 'ALTER TABLE team ADD COLUMN company_id INTEGER'),
|
||||
('project', 'ALTER TABLE project ADD COLUMN company_id INTEGER')
|
||||
]
|
||||
|
||||
|
||||
for table_name, sql_command in tables_to_migrate:
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = [column[1] for column in cursor.fetchall()]
|
||||
if 'company_id' not in columns:
|
||||
print(f"Adding company_id column to {table_name}...")
|
||||
cursor.execute(sql_command)
|
||||
|
||||
|
||||
# Check if there are existing users but no companies
|
||||
cursor.execute("SELECT COUNT(*) FROM user")
|
||||
user_count = cursor.fetchone()[0]
|
||||
|
||||
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='company'")
|
||||
company_table_exists = cursor.fetchone()
|
||||
|
||||
|
||||
if user_count > 0 and company_table_exists:
|
||||
cursor.execute("SELECT COUNT(*) FROM company")
|
||||
company_count = cursor.fetchone()[0]
|
||||
|
||||
|
||||
if company_count == 0:
|
||||
print("Creating default company for existing data...")
|
||||
# Create default company
|
||||
@@ -349,20 +349,20 @@ def migrate_to_company_model():
|
||||
INSERT INTO company (name, slug, description, is_active, max_users)
|
||||
VALUES ('Default Organization', 'default', 'Migrated from single-tenant installation', 1, 1000)
|
||||
""")
|
||||
|
||||
|
||||
# Get the company ID
|
||||
cursor.execute("SELECT last_insert_rowid()")
|
||||
company_id = cursor.fetchone()[0]
|
||||
|
||||
|
||||
# Update all existing records to use the default company
|
||||
cursor.execute(f"UPDATE user SET company_id = {company_id} WHERE company_id IS NULL")
|
||||
cursor.execute(f"UPDATE team SET company_id = {company_id} WHERE company_id IS NULL")
|
||||
cursor.execute(f"UPDATE project SET company_id = {company_id} WHERE company_id IS NULL")
|
||||
|
||||
|
||||
print(f"Assigned {user_count} existing users to default company")
|
||||
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during company migration: {e}")
|
||||
conn.rollback()
|
||||
@@ -381,7 +381,7 @@ def init_system_settings():
|
||||
)
|
||||
db.session.add(reg_setting)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
if not SystemSettings.query.filter_by(key='email_verification_required').first():
|
||||
print("Adding email_verification_required system setting...")
|
||||
email_setting = SystemSettings(
|
||||
@@ -398,7 +398,7 @@ def migrate_data():
|
||||
if Company.query.count() == 0:
|
||||
print("No companies exist, skipping admin user creation. Use company setup instead.")
|
||||
return
|
||||
|
||||
|
||||
# Check if admin user exists in the first company
|
||||
default_company = Company.query.first()
|
||||
if default_company:
|
||||
@@ -466,7 +466,7 @@ def migrate_data():
|
||||
'description': 'Customer service and technical support activities'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
for proj_data in sample_projects:
|
||||
project = Project(
|
||||
name=proj_data['name'],
|
||||
@@ -477,7 +477,7 @@ def migrate_data():
|
||||
is_active=True
|
||||
)
|
||||
db.session.add(project)
|
||||
|
||||
|
||||
print(f"Created {len(sample_projects)} sample projects for {default_company.name}")
|
||||
|
||||
db.session.commit()
|
||||
@@ -560,17 +560,17 @@ def company_required(f):
|
||||
def decorated_function(*args, **kwargs):
|
||||
if g.user is None:
|
||||
return redirect(url_for('login', next=request.url))
|
||||
|
||||
|
||||
if g.user.company_id is None:
|
||||
flash('You must be associated with a company to access this page.', 'error')
|
||||
return redirect(url_for('setup_company'))
|
||||
|
||||
|
||||
# Set company context
|
||||
g.company = Company.query.get(g.user.company_id)
|
||||
if not g.company or not g.company.is_active:
|
||||
flash('Your company is not active. Please contact support.', 'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
@@ -588,7 +588,7 @@ def load_logged_in_user():
|
||||
g.company = Company.query.get(g.user.company_id)
|
||||
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
|
||||
@@ -621,7 +621,7 @@ def home():
|
||||
available_projects = []
|
||||
if g.user.company_id:
|
||||
all_projects = Project.query.filter_by(
|
||||
company_id=g.user.company_id,
|
||||
company_id=g.user.company_id,
|
||||
is_active=True
|
||||
).all()
|
||||
for project in all_projects:
|
||||
@@ -633,7 +633,7 @@ def home():
|
||||
history=history,
|
||||
available_projects=available_projects)
|
||||
else:
|
||||
return render_template('index.html', title='Home')
|
||||
return render_template('about.html', title='Home')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
@@ -729,14 +729,14 @@ def register():
|
||||
error = 'Passwords do not match'
|
||||
elif not company_code:
|
||||
error = 'Company code is required'
|
||||
|
||||
|
||||
# Find company by code
|
||||
company = None
|
||||
if company_code:
|
||||
company = Company.query.filter_by(slug=company_code.lower()).first()
|
||||
if not company:
|
||||
error = 'Invalid company code'
|
||||
|
||||
|
||||
# Check for existing users within the company
|
||||
if company and not error:
|
||||
if User.query.filter_by(username=username, company_id=company.id).first():
|
||||
@@ -748,13 +748,13 @@ def register():
|
||||
try:
|
||||
# Check if this is the first user account in this company
|
||||
is_first_user_in_company = User.query.filter_by(company_id=company.id).count() == 0
|
||||
|
||||
|
||||
# Check if email verification is required
|
||||
email_verification_required = get_system_setting('email_verification_required', 'true') == 'true'
|
||||
|
||||
new_user = User(
|
||||
username=username,
|
||||
email=email,
|
||||
username=username,
|
||||
email=email,
|
||||
company_id=company.id,
|
||||
is_verified=False
|
||||
)
|
||||
@@ -818,17 +818,17 @@ The TimeTrack Team
|
||||
def setup_company():
|
||||
"""Company setup route for creating new companies with admin users"""
|
||||
existing_companies = Company.query.count()
|
||||
|
||||
|
||||
# Determine access level
|
||||
is_initial_setup = existing_companies == 0
|
||||
is_super_admin = g.user and g.user.role == Role.ADMIN and existing_companies > 0
|
||||
is_authorized = is_initial_setup or is_super_admin
|
||||
|
||||
|
||||
# Check authorization for non-initial setups
|
||||
if not is_initial_setup and not is_super_admin:
|
||||
flash('You do not have permission to create new companies.', 'error')
|
||||
return redirect(url_for('home') if g.user else url_for('login'))
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
company_name = request.form.get('company_name')
|
||||
company_description = request.form.get('company_description', '')
|
||||
@@ -836,7 +836,7 @@ def setup_company():
|
||||
admin_email = request.form.get('admin_email')
|
||||
admin_password = request.form.get('admin_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
|
||||
# Validate input
|
||||
error = None
|
||||
if not company_name:
|
||||
@@ -851,21 +851,21 @@ def setup_company():
|
||||
error = 'Passwords do not match'
|
||||
elif len(admin_password) < 6:
|
||||
error = 'Password must be at least 6 characters long'
|
||||
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
# Generate company slug
|
||||
import re
|
||||
slug = re.sub(r'[^\w\s-]', '', company_name.lower())
|
||||
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
|
||||
|
||||
|
||||
# Ensure slug uniqueness
|
||||
base_slug = slug
|
||||
counter = 1
|
||||
while Company.query.filter_by(slug=slug).first():
|
||||
slug = f"{base_slug}-{counter}"
|
||||
counter += 1
|
||||
|
||||
|
||||
# Create company
|
||||
company = Company(
|
||||
name=company_name,
|
||||
@@ -875,22 +875,22 @@ def setup_company():
|
||||
)
|
||||
db.session.add(company)
|
||||
db.session.flush() # Get company.id without committing
|
||||
|
||||
|
||||
# Check if username/email already exists in this company context
|
||||
existing_user_by_username = User.query.filter_by(
|
||||
username=admin_username,
|
||||
username=admin_username,
|
||||
company_id=company.id
|
||||
).first()
|
||||
existing_user_by_email = User.query.filter_by(
|
||||
email=admin_email,
|
||||
email=admin_email,
|
||||
company_id=company.id
|
||||
).first()
|
||||
|
||||
|
||||
if existing_user_by_username:
|
||||
error = 'Username already exists in this company'
|
||||
elif existing_user_by_email:
|
||||
error = 'Email already registered in this company'
|
||||
|
||||
|
||||
if error is None:
|
||||
# Create admin user
|
||||
admin_user = User(
|
||||
@@ -903,13 +903,13 @@ def setup_company():
|
||||
admin_user.set_password(admin_password)
|
||||
db.session.add(admin_user)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
if is_initial_setup:
|
||||
# Auto-login the admin user for initial setup
|
||||
session['user_id'] = admin_user.id
|
||||
session['username'] = admin_user.username
|
||||
session['role'] = admin_user.role.value
|
||||
|
||||
|
||||
flash(f'Company "{company_name}" created successfully! You are now logged in as the administrator.', 'success')
|
||||
return redirect(url_for('home'))
|
||||
else:
|
||||
@@ -918,17 +918,17 @@ def setup_company():
|
||||
return redirect(url_for('admin_company') if g.user else url_for('login'))
|
||||
else:
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Error during company setup: {str(e)}")
|
||||
error = f"An error occurred during setup: {str(e)}"
|
||||
|
||||
|
||||
if error:
|
||||
flash(error, 'error')
|
||||
|
||||
return render_template('setup_company.html',
|
||||
title='Company Setup',
|
||||
|
||||
return render_template('setup_company.html',
|
||||
title='Company Setup',
|
||||
existing_companies=existing_companies,
|
||||
is_initial_setup=is_initial_setup,
|
||||
is_super_admin=is_super_admin)
|
||||
@@ -1311,7 +1311,6 @@ def verify_2fa():
|
||||
return render_template('verify_2fa.html', title='Two-Factor Authentication')
|
||||
|
||||
@app.route('/about')
|
||||
@login_required
|
||||
def about():
|
||||
return render_template('about.html', title='About')
|
||||
|
||||
@@ -1625,13 +1624,13 @@ def admin_settings():
|
||||
reg_setting = SystemSettings.query.filter_by(key='registration_enabled').first()
|
||||
if reg_setting:
|
||||
reg_setting.value = 'true' if registration_enabled else 'false'
|
||||
|
||||
|
||||
# Update email verification setting
|
||||
email_verification_required = 'email_verification_required' in request.form
|
||||
email_setting = SystemSettings.query.filter_by(key='email_verification_required').first()
|
||||
if email_setting:
|
||||
email_setting.value = 'true' if email_verification_required else 'false'
|
||||
|
||||
|
||||
db.session.commit()
|
||||
flash('System settings updated successfully!', 'success')
|
||||
|
||||
@@ -1652,7 +1651,7 @@ def admin_settings():
|
||||
def admin_company():
|
||||
"""View and manage company settings"""
|
||||
company = g.company
|
||||
|
||||
|
||||
# Get company statistics
|
||||
stats = {
|
||||
'total_users': User.query.filter_by(company_id=company.id).count(),
|
||||
@@ -1660,7 +1659,7 @@ def admin_company():
|
||||
'total_projects': Project.query.filter_by(company_id=company.id).count(),
|
||||
'active_projects': Project.query.filter_by(company_id=company.id, is_active=True).count(),
|
||||
}
|
||||
|
||||
|
||||
return render_template('admin_company.html', title='Company Management', company=company, stats=stats)
|
||||
|
||||
@app.route('/admin/company/edit', methods=['GET', 'POST'])
|
||||
@@ -1669,20 +1668,20 @@ def admin_company():
|
||||
def edit_company():
|
||||
"""Edit company details"""
|
||||
company = g.company
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
description = request.form.get('description', '')
|
||||
max_users = request.form.get('max_users')
|
||||
is_active = 'is_active' in request.form
|
||||
|
||||
|
||||
# Validate input
|
||||
error = None
|
||||
if not name:
|
||||
error = 'Company name is required'
|
||||
elif name != company.name and Company.query.filter_by(name=name).first():
|
||||
error = 'Company name already exists'
|
||||
|
||||
|
||||
if max_users:
|
||||
try:
|
||||
max_users = int(max_users)
|
||||
@@ -1692,19 +1691,19 @@ def edit_company():
|
||||
error = 'Maximum users must be a valid number'
|
||||
else:
|
||||
max_users = None
|
||||
|
||||
|
||||
if error is None:
|
||||
company.name = name
|
||||
company.description = description
|
||||
company.max_users = max_users
|
||||
company.is_active = is_active
|
||||
db.session.commit()
|
||||
|
||||
|
||||
flash('Company details updated successfully!', 'success')
|
||||
return redirect(url_for('admin_company'))
|
||||
else:
|
||||
flash(error, 'error')
|
||||
|
||||
|
||||
return render_template('edit_company.html', title='Edit Company', company=company)
|
||||
|
||||
@app.route('/admin/company/users')
|
||||
@@ -1713,7 +1712,7 @@ def edit_company():
|
||||
def company_users():
|
||||
"""List all users in the company with detailed information"""
|
||||
users = User.query.filter_by(company_id=g.company.id).order_by(User.created_at.desc()).all()
|
||||
|
||||
|
||||
# Calculate user statistics
|
||||
user_stats = {
|
||||
'total': len(users),
|
||||
@@ -1726,8 +1725,8 @@ def company_users():
|
||||
'team_leaders': len([u for u in users if u.role == Role.TEAM_LEADER]),
|
||||
'team_members': len([u for u in users if u.role == Role.TEAM_MEMBER]),
|
||||
}
|
||||
|
||||
return render_template('company_users.html', title='Company Users',
|
||||
|
||||
return render_template('company_users.html', title='Company Users',
|
||||
users=users, stats=user_stats, company=g.company)
|
||||
|
||||
# Add these routes for team management
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="about-content">
|
||||
<div class="intro">
|
||||
<h2>Professional Time Tracking Made Simple</h2>
|
||||
<p>TimeTrack is a comprehensive time management solution designed to help organizations and individuals monitor work hours efficiently. Built with simplicity and accuracy in mind, our platform provides the tools you need to track, manage, and analyze time spent on work activities.</p>
|
||||
<p>TimeTrack is a comprehensive multi-tenant time management solution designed to help organizations and individuals monitor work hours efficiently. Built with simplicity and accuracy in mind, our platform provides the tools you need to track, manage, and analyze time spent on work activities across multiple companies and teams.</p>
|
||||
</div>
|
||||
|
||||
<div class="features-section">
|
||||
@@ -37,6 +37,11 @@
|
||||
<p>Customize work hour requirements, mandatory break durations, and threshold settings to match your organization's policies and labor regulations.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<h3>🏢 Multi-Company Support</h3>
|
||||
<p>Enterprise-ready multi-tenant architecture allows multiple companies to operate independently within a single TimeTrack instance, with complete data isolation and security.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<h3>🔐 Secure & Reliable</h3>
|
||||
<p>Built with security best practices, user authentication, email verification, and role-based access control to protect your organization's data.</p>
|
||||
@@ -65,7 +70,7 @@
|
||||
|
||||
<div class="role-card">
|
||||
<h3>⚙️ Administrator</h3>
|
||||
<p>Full system access including user management, team configuration, system settings, and complete administrative control.</p>
|
||||
<p>Full company access including user management, team configuration, system settings, and complete administrative control within their company. Can also manage company information and setup new user registration.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +91,7 @@
|
||||
|
||||
<div class="benefit">
|
||||
<h3>✅ Scalable Solution</h3>
|
||||
<p>Grows with your organization from small teams to large enterprises. Role-based architecture supports complex organizational structures.</p>
|
||||
<p>Grows with your organization from small teams to large enterprises. Multi-tenant architecture supports multiple companies, complex organizational structures, and unlimited growth potential.</p>
|
||||
</div>
|
||||
|
||||
<div class="benefit">
|
||||
@@ -101,6 +106,21 @@
|
||||
<p>TimeTrack is built using modern web technologies including Flask (Python), SQLite database, and responsive HTML/CSS/JavaScript frontend. The application features a REST API architecture, secure session management, and email integration for user verification.</p>
|
||||
</div>
|
||||
|
||||
<div class="company-setup-section">
|
||||
<h2>Company Setup & Registration</h2>
|
||||
<div class="setup-options">
|
||||
<div class="setup-option">
|
||||
<h3>🏢 New Company Setup</h3>
|
||||
<p>Setting up TimeTrack for the first time or adding a new company? Create your company profile and administrator account to get started.</p>
|
||||
</div>
|
||||
|
||||
<div class="setup-option">
|
||||
<h3>👥 Join Existing Company</h3>
|
||||
<p>Already have a company using TimeTrack? Get your company code from your administrator and register to join your organization.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="support-section">
|
||||
<h2>Getting Started</h2>
|
||||
<p>Ready to start tracking your time more effectively? <a href="{{ url_for('register') }}">Create an account</a> to begin, or <a href="{{ url_for('login') }}">log in</a> if you already have access. For questions or support, contact your system administrator.</p>
|
||||
@@ -216,14 +236,49 @@
|
||||
color: #cceeff;
|
||||
}
|
||||
|
||||
.company-setup-section {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
.setup-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.setup-option {
|
||||
background: #f0f8ff;
|
||||
border: 1px solid #b3d9ff;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.setup-option h3 {
|
||||
color: #0066cc;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.setup-option p {
|
||||
margin-bottom: 0;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.feature-grid, .role-cards, .benefits-list {
|
||||
.feature-grid, .role-cards, .benefits-list, .setup-options {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.benefits-list .benefit {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.setup-options .setup-option {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user