287 lines
9.9 KiB
Python
287 lines
9.9 KiB
Python
from flask import Flask, render_template, request, session, redirect, url_for, make_response, jsonify, flash
|
|
from flask_assets import Environment, Bundle
|
|
from flask_mail import Mail, Message
|
|
from translation_manager import init_app, translate, get_current_language, create_language_selector, set_language
|
|
import os
|
|
import re
|
|
from datetime import datetime
|
|
from xml.etree import ElementTree as ET
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
|
|
|
|
# Email configuration
|
|
app.config['MAIL_SERVER'] = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')
|
|
app.config['MAIL_PORT'] = int(os.environ.get('MAIL_PORT', 587))
|
|
app.config['MAIL_USE_TLS'] = os.environ.get('MAIL_USE_TLS', 'True').lower() == 'true'
|
|
app.config['MAIL_USE_SSL'] = os.environ.get('MAIL_USE_SSL', 'False').lower() == 'true'
|
|
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME', 'contact@kobelly.be')
|
|
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD', '')
|
|
app.config['MAIL_DEFAULT_SENDER'] = os.environ.get('MAIL_DEFAULT_SENDER', 'contact@kobelly.be')
|
|
|
|
# Initialize Flask-Mail
|
|
mail = Mail(app)
|
|
|
|
def validate_email(email):
|
|
"""Validate email format"""
|
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return re.match(pattern, email) is not None
|
|
|
|
def validate_phone(phone):
|
|
"""Validate phone number format (basic validation)"""
|
|
if not phone:
|
|
return True # Phone is optional
|
|
# Remove all non-digit characters
|
|
digits_only = re.sub(r'\D', '', phone)
|
|
return len(digits_only) >= 8
|
|
|
|
def send_contact_email(form_data):
|
|
"""Send contact form email"""
|
|
try:
|
|
# Create email content
|
|
subject = f"New Contact Form Submission from {form_data['firstName']} {form_data['lastName']}"
|
|
|
|
# HTML email body
|
|
html_body = f"""
|
|
<html>
|
|
<body>
|
|
<h2>New Contact Form Submission</h2>
|
|
<p><strong>Name:</strong> {form_data['firstName']} {form_data['lastName']}</p>
|
|
<p><strong>Email:</strong> {form_data['email']}</p>
|
|
<p><strong>Phone:</strong> {form_data.get('phone', 'Not provided')}</p>
|
|
<p><strong>Company:</strong> {form_data.get('company', 'Not provided')}</p>
|
|
<p><strong>Service Interested In:</strong> {form_data['service']}</p>
|
|
<p><strong>Budget:</strong> {form_data.get('budget', 'Not specified')}</p>
|
|
<p><strong>Message:</strong></p>
|
|
<p>{form_data['message'].replace(chr(10), '<br>')}</p>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Plain text email body
|
|
text_body = f"""
|
|
New Contact Form Submission
|
|
|
|
Name: {form_data['firstName']} {form_data['lastName']}
|
|
Email: {form_data['email']}
|
|
Phone: {form_data.get('phone', 'Not provided')}
|
|
Company: {form_data.get('company', 'Not provided')}
|
|
Service Interested In: {form_data['service']}
|
|
Budget: {form_data.get('budget', 'Not specified')}
|
|
|
|
Message:
|
|
{form_data['message']}
|
|
"""
|
|
|
|
# Create message
|
|
msg = Message(
|
|
subject=subject,
|
|
recipients=[app.config['MAIL_DEFAULT_SENDER']],
|
|
html=html_body,
|
|
body=text_body
|
|
)
|
|
|
|
# Send email
|
|
mail.send(msg)
|
|
return True, "Email sent successfully"
|
|
|
|
except Exception as e:
|
|
return False, f"Failed to send email: {str(e)}"
|
|
|
|
# Initialize Flask-Assets
|
|
assets = Environment(app)
|
|
assets.url = app.static_url_path
|
|
|
|
# Configure assets for production
|
|
if not app.debug:
|
|
assets.auto_build = False
|
|
assets.cache = True
|
|
assets.manifest = 'file'
|
|
|
|
# Register asset bundles
|
|
css_bundle = assets.register('css_all', Bundle(
|
|
'css/main.css',
|
|
filters='cssmin',
|
|
output='css/main.min.css'
|
|
))
|
|
|
|
js_bundle = assets.register('js_all', Bundle(
|
|
'js/main.js',
|
|
filters='jsmin',
|
|
output='js/main.min.js'
|
|
))
|
|
|
|
# Initialize the translation system
|
|
init_app(app)
|
|
|
|
# WWW to non-WWW redirect
|
|
@app.before_request
|
|
def redirect_www():
|
|
"""Redirect www subdomain to non-www version"""
|
|
if request.host.startswith('www.'):
|
|
# Get the non-www version of the host
|
|
non_www_host = request.host.replace('www.', '', 1)
|
|
# Preserve the full URL including path and query parameters
|
|
url = request.url.replace(request.host, non_www_host)
|
|
return redirect(url, code=301)
|
|
|
|
# Context processor to make variables available in templates
|
|
@app.context_processor
|
|
def inject_conf_var():
|
|
return dict(
|
|
translate=translate,
|
|
get_current_language=get_current_language,
|
|
create_language_selector=create_language_selector,
|
|
t=translate, # Short alias
|
|
current_year=datetime.now().year
|
|
)
|
|
|
|
def generate_sitemap():
|
|
"""Generate XML sitemap for the website"""
|
|
root = ET.Element("urlset")
|
|
root.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
|
|
|
|
# Define the main pages with their priorities and change frequencies
|
|
pages = [
|
|
{'url': '/', 'priority': '1.0', 'changefreq': 'weekly'},
|
|
{'url': '/services', 'priority': '0.9', 'changefreq': 'monthly'},
|
|
{'url': '/about', 'priority': '0.8', 'changefreq': 'monthly'},
|
|
{'url': '/contact', 'priority': '0.7', 'changefreq': 'monthly'},
|
|
{'url': '/portfolio', 'priority': '0.8', 'changefreq': 'weekly'},
|
|
]
|
|
|
|
base_url = "https://kobelly.com" # Change this to your actual domain
|
|
|
|
for page in pages:
|
|
url_elem = ET.SubElement(root, "url")
|
|
loc = ET.SubElement(url_elem, "loc")
|
|
loc.text = base_url + page['url']
|
|
|
|
lastmod = ET.SubElement(url_elem, "lastmod")
|
|
lastmod.text = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
changefreq = ET.SubElement(url_elem, "changefreq")
|
|
changefreq.text = page['changefreq']
|
|
|
|
priority = ET.SubElement(url_elem, "priority")
|
|
priority.text = page['priority']
|
|
|
|
return ET.tostring(root, encoding='unicode', method='xml')
|
|
|
|
@app.route('/sitemap.xml')
|
|
def sitemap():
|
|
"""Serve the XML sitemap"""
|
|
sitemap_xml = generate_sitemap()
|
|
response = make_response(sitemap_xml)
|
|
response.headers["Content-Type"] = "application/xml"
|
|
return response
|
|
|
|
@app.route('/')
|
|
def index():
|
|
print("Current language in index:", get_current_language())
|
|
return render_template('index.html')
|
|
|
|
@app.route('/services')
|
|
def services():
|
|
return render_template('services.html')
|
|
|
|
@app.route('/contact')
|
|
def contact():
|
|
return render_template('contact.html')
|
|
|
|
@app.route('/contact', methods=['POST'])
|
|
def submit_contact():
|
|
"""Handle contact form submission"""
|
|
try:
|
|
# Get form data
|
|
data = request.get_json()
|
|
|
|
# Validate required fields
|
|
required_fields = ['firstName', 'lastName', 'email', 'service', 'message']
|
|
for field in required_fields:
|
|
if not data.get(field) or not data[field].strip():
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'{field.replace("firstName", "First name").replace("lastName", "Last name").title()} is required'
|
|
}), 400
|
|
|
|
# Validate email format
|
|
if not validate_email(data['email']):
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Please enter a valid email address'
|
|
}), 400
|
|
|
|
# Validate phone if provided
|
|
if data.get('phone') and not validate_phone(data['phone']):
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Please enter a valid phone number'
|
|
}), 400
|
|
|
|
# Prepare form data
|
|
form_data = {
|
|
'firstName': data['firstName'].strip(),
|
|
'lastName': data['lastName'].strip(),
|
|
'email': data['email'].strip(),
|
|
'phone': data.get('phone', '').strip(),
|
|
'company': data.get('company', '').strip(),
|
|
'service': data['service'].strip(),
|
|
'budget': data.get('budget', '').strip(),
|
|
'message': data['message'].strip()
|
|
}
|
|
|
|
# Send email
|
|
success, message = send_contact_email(form_data)
|
|
|
|
if success:
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Thank you for your message! I will get back to you within 48 hours.'
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Sorry, there was an error sending your message. Please try again or contact me directly.'
|
|
}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'An unexpected error occurred. Please try again.'
|
|
}), 500
|
|
|
|
@app.route('/about')
|
|
def about():
|
|
return render_template('about.html')
|
|
|
|
@app.route('/portfolio')
|
|
def portfolio():
|
|
return render_template('portfolio.html')
|
|
|
|
@app.route('/set_language/<language>')
|
|
def set_language_route(language):
|
|
"""Set the language and redirect back to the previous page"""
|
|
if set_language(language):
|
|
# Redirect back to the referring page or home
|
|
return redirect(request.referrer or url_for('index'))
|
|
return redirect(url_for('index'))
|
|
|
|
@app.route('/build-assets')
|
|
def build_assets():
|
|
"""Build minified assets with cache busting"""
|
|
if app.debug:
|
|
try:
|
|
# Build assets
|
|
css_bundle.build()
|
|
js_bundle.build()
|
|
return {'status': 'success', 'message': 'Assets built successfully'}
|
|
except Exception as e:
|
|
return {'status': 'error', 'message': str(e)}, 500
|
|
else:
|
|
return {'status': 'error', 'message': 'Asset building only available in debug mode'}, 403
|
|
|
|
if __name__ == '__main__':
|
|
# Check environment variables for debug mode
|
|
debug_mode = os.environ.get('FLASK_DEBUG', '1') == '1' and os.environ.get('FLASK_ENV') != 'production'
|
|
app.run(debug=debug_mode, host='0.0.0.0', port=5000) |