Files
conference/templates/attendee/profile.html
T
paul 335658f2bf Update templates and styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 15:21:10 +00:00

353 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ 'profile'|t }} - NetEvents{% endblock %}
{% block extra_styles %}
<style>
.profile-page {
max-width: 900px;
margin: 0 auto;
padding: 50px 40px;
}
.profile-container {
display: flex;
gap: 30px;
align-items: flex-start;
}
.photo-box {
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 10px;
padding: 20px;
text-align: center;
}
.photo-box h3 {
margin-bottom: 15px;
color: #2c3e50;
}
.profile-photo-section {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
{% endblock %}
{% block content %}
<div class="profile-page">
<h1>{{ 'my_profile'|t }}</h1>
<div class="profile-container">
<div class="photo-box">
<h3>{{ 'profile_photo'|t }}</h3>
<div class="profile-photo-section">
<div class="current-photo" id="photo-container">
{% if attendee.profile_picture %}
<img src="{{ url_for('static', filename='uploads/' + attendee.profile_picture) }}" alt="{{ 'profile_photo'|t }}" class="profile-img" id="profile-preview">
{% else %}
<img src="" alt="{{ 'profile_preview'|t }}" class="profile-img" id="profile-preview" style="display: none;">
<div class="no-photo" id="no-photo">{{ 'no_photo'|t }}</div>
{% endif %}
</div>
<canvas id="crop-canvas" style="display: none;"></canvas>
<div class="zoom-controls">
<button type="button" class="btn btn-sm btn-outline" onclick="adjustZoom(-0.25)">-</button>
<span id="zoom-level">100%</span>
<button type="button" class="btn btn-sm btn-outline" onclick="adjustZoom(0.25)">+</button>
<button type="button" class="btn btn-sm btn-outline" onclick="handleFormSubmit()">{{ 'save'|t }}</button>
</div>
<div class="form-group">
<label for="photo">{{ 'upload_photo'|t }}</label>
<input type="file" id="photo" name="photo" accept="image/*">
</div>
</div>
</div>
<form method="POST" action="{{ url_for('attendee_profile') }}" class="profile-form">
<div class="form-group">
<label for="first_name">{{ 'first_name'|t }}</label>
<input type="text" id="first_name" name="first_name" value="{{ attendee.first_name }}" required>
</div>
<div class="form-group">
<label for="last_name">{{ 'last_name'|t }}</label>
<input type="text" id="last_name" name="last_name" value="{{ attendee.last_name }}" required>
</div>
<div class="form-group">
<label for="organisation">{{ 'organisation'|t }}</label>
<input type="text" id="organisation" name="organisation" value="{{ attendee.organisation or '' }}">
</div>
<div class="form-group">
<label for="role">{{ 'role_profession'|t }}</label>
<input type="text" id="role" name="role" value="{{ attendee.role or '' }}">
</div>
<div class="form-group">
<label for="phone">{{ 'phone'|t }}</label>
<input type="tel" id="phone" name="phone" value="{{ attendee.phone or '' }}">
</div>
<div class="form-group">
<label for="linkedin">{{ 'linkedin'|t }}</label>
<input type="url" id="linkedin" name="linkedin" value="{{ attendee.linkedin or '' }}">
</div>
<div class="form-group">
<label for="introduction">{{ 'short_introduction'|t }}</label>
<textarea id="introduction" name="introduction" rows="4">{{ attendee.introduction or '' }}</textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{ 'update_profile'|t }}</button>
</div>
</form>
</div>
</div>
<style>
.current-photo {
width: 150px;
height: 150px;
margin-bottom: 10px;
overflow: hidden;
border-radius: 0;
position: relative;
background: #e0e0e0;
}
.profile-img {
position: absolute;
border-radius: 0;
cursor: move;
}
.no-photo, #no-photo {
width: 150px;
height: 150px;
border-radius: 50%;
background: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
color: #888;
font-size: 14px;
}
.zoom-controls {
display: flex;
gap: 5px;
align-items: center;
margin-bottom: 10px;
}
</style>
<script>
var zoomLevel = 1.0;
var translateX = 0;
var translateY = 0;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var originalImage = null;
var containerSize = 150;
var container, preview, photoInput;
function init() {
container = document.getElementById('photo-container');
preview = document.getElementById('profile-preview');
photoInput = document.getElementById('photo');
if (photoInput) {
photoInput.addEventListener('change', handleFileSelect);
}
if (container && preview) {
container.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', doDrag);
document.addEventListener('mouseup', endDrag);
container.addEventListener('wheel', handleWheel);
}
}
function handleFileSelect(e) {
var file = e.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(event) {
var img = new Image();
img.onload = function() {
originalImage = img;
zoomLevel = 1.0;
translateX = 0;
translateY = 0;
preview.src = event.target.result;
preview.style.display = 'block';
var noPhoto = document.getElementById('no-photo');
if (noPhoto) noPhoto.style.display = 'none';
updateDisplay();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
function adjustZoom(delta) {
var oldZoom = zoomLevel;
zoomLevel = Math.max(1.0, Math.min(3.0, zoomLevel + delta));
document.getElementById('zoom-level').textContent = Math.round(zoomLevel * 100) + '%';
// Adjust translation to zoom toward center
var factor = zoomLevel / oldZoom;
translateX *= factor;
translateY *= factor;
updateDisplay();
}
function updateDisplay() {
if (!preview || !originalImage) return;
var img = originalImage;
var imgW = img.naturalWidth;
var imgH = img.naturalHeight;
var scale, dispW, dispH, offsetX, offsetY;
if (imgW >= imgH) {
scale = containerSize / imgH;
dispH = containerSize;
dispW = imgW * scale;
offsetX = (containerSize - dispW) / 2;
offsetY = 0;
} else {
scale = containerSize / imgW;
dispW = containerSize;
dispH = imgH * scale;
offsetX = 0;
offsetY = (containerSize - dispH) / 2;
}
// Explicitly set layout dimensions so transform origin centers correctly
preview.style.width = dispW + 'px';
preview.style.height = dispH + 'px';
preview.style.transform = 'translate(' + (translateX + offsetX) + 'px, ' + (translateY + offsetY) + 'px) scale(' + zoomLevel + ')';
}
function startDrag(e) {
if (!originalImage || zoomLevel <= 1.0) return;
isDragging = true;
dragStartX = e.clientX - translateX;
dragStartY = e.clientY - translateY;
preview.style.cursor = 'grabbing';
e.preventDefault();
}
function doDrag(e) {
if (!isDragging) return;
translateX = e.clientX - dragStartX;
translateY = e.clientY - dragStartY;
updateDisplay();
}
function endDrag() {
isDragging = false;
if (preview) preview.style.cursor = zoomLevel > 1.0 ? 'move' : 'default';
}
function handleWheel(e) {
e.preventDefault();
var rect = container.getBoundingClientRect();
var mouseX = e.clientX - rect.left - containerSize / 2;
var mouseY = e.clientY - rect.top - containerSize / 2;
var oldZoom = zoomLevel;
zoomLevel = Math.max(1.0, Math.min(3.0, zoomLevel - e.deltaY * 0.01));
// Zoom toward mouse position
translateX = mouseX - (mouseX - translateX) * (zoomLevel / oldZoom);
translateY = mouseY - (mouseY - translateY) * (zoomLevel / oldZoom);
document.getElementById('zoom-level').textContent = Math.round(zoomLevel * 100) + '%';
updateDisplay();
}
function handleFormSubmit(e) {
if (e) e.preventDefault();
if (!originalImage) {
alert('Please select an image first');
return;
}
var canvas = document.getElementById('crop-canvas');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
var img = originalImage;
var imgW = img.naturalWidth;
var imgH = img.naturalHeight;
var scale, dispW, dispH, offsetX, offsetY;
if (imgW >= imgH) {
scale = containerSize / imgH;
dispH = containerSize;
dispW = imgW * scale;
offsetX = (containerSize - dispW) / 2;
offsetY = 0;
} else {
scale = containerSize / imgW;
dispW = containerSize;
dispH = imgH * scale;
offsetX = 0;
offsetY = (containerSize - dispH) / 2;
}
// NEW CROP CALCULATION
// Display: translate(translateX + offsetX, translateY + offsetY) scale(zoomLevel) with transform-origin: center
// Container pixel (cx, cy) maps to element point: (cx - tx - ox) / zoom + (ox + dispW/2)
// Element point maps to source: element_point / scale
// Simplifying: srcX = (cx - tx - ox) / zoom / scale + dispW/2 / scale
var srcX = (0 - translateX - offsetX) / scale / zoomLevel + dispW / 2 / scale;
var srcY = (0 - translateY - offsetY) / scale / zoomLevel + dispH / 2 / scale;
var srcW = containerSize / scale / zoomLevel;
var srcH = containerSize / scale / zoomLevel;
// Clamp to image bounds to prevent out-of-bounds transparent drawing
srcX = Math.max(0, Math.min(srcX, imgW - srcW));
srcY = Math.max(0, Math.min(srcY, imgH - srcH));
srcW = Math.min(srcW, imgW - srcX);
srcH = Math.min(srcH, imgH - srcY);
ctx.clearRect(0, 0, 300, 300);
// Draw exactly the visible area scaled up to the 300x300 canvas
ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, 300, 300);
canvas.toBlob(function(blob) {
var formData = new FormData();
formData.append('photo', blob, 'profile.jpg');
fetch('{{ url_for("upload_photo") }}', {
method: 'POST',
body: formData
}).then(function(response) {
return response.json();
}).then(function(data) {
if (data.success) {
location.reload();
} else {
alert(data.error || 'Upload failed');
}
}).catch(function(error) {
alert('Error uploading photo');
});
}, 'image/jpeg', 0.9);
}
document.addEventListener('DOMContentLoaded', init);
</script>
{% endblock %}