{% extends "base.twig" %}
{% block title %}{{ t('ui.profile.title', {}, 'common') }} - {{ parent() }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>
<i class="fas fa-user"></i>
{{ t('ui.profile.my_profile', {}, 'common') }}
</h2>
<a href="/profile/{{ user_username|url_encode }}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-id-card me-1"></i>
{{ t('ui.profile.view_profile', {}, 'common') }}
</a>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">{{ t('ui.profile.profile_information', {}, 'common') }}</h5>
</div>
<div class="card-body">
<form id="profileForm">
<div class="mb-3">
<label for="username" class="form-label">{{ t('ui.profile.username', {}, 'common') }}</label>
<input type="text" class="form-control" id="username" name="username" value="{{ user_username }}" readonly>
<div class="form-text">{{ t('ui.profile.username_readonly_help', {}, 'common') }}</div>
</div>
<div class="mb-3">
<label for="realName" class="form-label">{{ t('ui.profile.real_name', {}, 'common') }}</label>
<input type="text" class="form-control" id="realName" name="real_name" value="{{ user_real_name }}" readonly maxlength="100">
<div class="form-text">{{ t('ui.profile.real_name_help', {}, 'common') }}</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">{{ t('ui.profile.email_address', {}, 'common') }}</label>
<input type="email" class="form-control" id="email" name="email" value="{{ user_email }}" maxlength="100">
<div class="form-text">{{ t('ui.profile.email_help', {}, 'common') }}</div>
</div>
<div class="mb-3">
<label for="location" class="form-label">{{ t('ui.profile.location', {}, 'common') }}</label>
<input type="text" class="form-control" id="location" name="location" value="{{ user_location }}" maxlength="100" placeholder="{{ t('ui.profile.location_placeholder', {}, 'common') }}">
<div class="form-text">{{ t('ui.profile.location_help', {}, 'common') }}</div>
</div>
<div class="mb-3">
<label for="aboutMe" class="form-label">{{ t('ui.profile.about_me', {}, 'common') }}</label>
<textarea class="form-control" id="aboutMe" name="about_me" rows="5" maxlength="2000" placeholder="{{ t('ui.profile.about_me_placeholder', {}, 'common') }}">{{ user_about_me }}</textarea>
<div class="form-text">{{ t('ui.profile.about_me_help', {}, 'common') }}</div>
</div>
<div class="mb-4">
<h6>{{ t('ui.profile.change_password', {}, 'common') }}</h6>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="currentPassword" class="form-label">{{ t('ui.profile.current_password', {}, 'common') }}</label>
<input type="password" class="form-control" id="currentPassword" name="current_password">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="newPassword" class="form-label">{{ t('ui.profile.new_password', {}, 'common') }}</label>
<input type="password" class="form-control" id="newPassword" name="new_password">
</div>
</div>
</div>
<div class="form-text">{{ t('ui.profile.password_blank_keep_help', {}, 'common') }}</div>
</div>
<div class="d-flex justify-content-between">
<div>
<small class="text-muted">
{{ t('ui.profile.account_created', {}, 'common') }}: {{ user_created_at|date('F j, Y') }}<br>
{{ t('ui.profile.last_login', {}, 'common') }}: {{ user_last_login ? user_last_login|date('F j, Y g:i A') : t('ui.profile.never', {}, 'common') }}
</small>
</div>
<button type="submit" class="btn btn-fidonet">
<i class="fas fa-save"></i>
{{ t('ui.profile.update_profile', {}, 'common') }}
</button>
</div>
</form>
<div id="profileStatus" class="mt-3" style="display: none;"></div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.profile.your_network_information', {}, 'common') }}</h6>
</div>
<div class="card-body">
{% if network_addresses|length > 0 %}
<div class="row g-1 mb-3">
{% for net in network_addresses %}
<div class="col-3"><span class="badge bg-secondary w-100 text-truncate" title="{{ net.domain }}">{{ net.domain }}</span></div>
<div class="col-3"><small class="text-muted">{{ net.address }}</small></div>
{% endfor %}
</div>
{% else %}
<p class="text-muted small mb-3">{{ t('ui.profile.no_networks_configured', {}, 'common') }}</p>
{% endif %}
<button type="button" class="btn btn-outline-secondary btn-sm w-100" data-bs-toggle="modal" data-bs-target="#netmailAddressesModal">
<i class="fas fa-address-card me-1"></i>
{{ t('ui.profile.show_netmail_addresses', {}, 'common') }}
</button>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.profile.activity_summary', {}, 'common') }}</h6>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-8">{{ t('ui.profile.credits', {}, 'common') }}:</dt>
<dd class="col-4">
{% if credits_enabled %}
{{ credits_symbol }}{{ credit_balance }}
{% else %}
<span class="text-muted">{{ t('ui.profile.disabled', {}, 'common') }}</span>
{% endif %}
</dd>
<dt class="col-8">{{ t('ui.profile.netmail_sent', {}, 'common') }}:</dt>
<dd class="col-4"><span id="netmailCount">-</span></dd>
<dt class="col-8">{{ t('ui.profile.echomail_posted', {}, 'common') }}:</dt>
<dd class="col-4"><span id="echomailCount">-</span></dd>
<dt class="col-8">{{ t('ui.profile.total_messages', {}, 'common') }}:</dt>
<dd class="col-4"><strong><span id="totalCount">-</span></strong></dd>
<dt class="col-8">{{ t('ui.profile.files_downloaded', {}, 'common') }}:</dt>
<dd class="col-4"><span id="filesDownloaded">-</span></dd>
<dt class="col-8">{{ t('ui.profile.files_uploaded', {}, 'common') }}:</dt>
<dd class="col-4"><span id="filesUploaded">-</span></dd>
<dt class="col-8">{{ t('ui.profile.transfer_ratio', {}, 'common') }}:</dt>
<dd class="col-4"><span id="transferRatio">-</span></dd>
</dl>
</div>
</div>
{% if credits_enabled %}
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.profile.credit_system', {}, 'common') }}</h6>
</div>
<div class="card-body">
<small class="text-muted d-block mb-2">{{ t('ui.profile.credit_costs_rewards', {}, 'common') }}</small>
<dl class="row mb-2 small">
<dt class="col-8">{{ t('ui.profile.daily_login', {}, 'common') }}:</dt>
<dd class="col-4 text-success">+{{ credits_config.daily_amount|default(25) }}</dd>
<dt class="col-8">{{ t('ui.profile.netmail_sent', {}, 'common') }}:</dt>
<dd class="col-4 text-danger">-{{ credits_config.netmail_cost|default(1) }}</dd>
<dt class="col-8">{{ t('ui.profile.crashmail', {}, 'common') }}:</dt>
<dd class="col-4 text-danger">-{{ credits_config.crashmail_cost|default(10) }}</dd>
<dt class="col-8">{{ t('ui.profile.echomail_posted', {}, 'common') }}:</dt>
<dd class="col-4 text-success">+{{ credits_config.echomail_reward|default(3) }}</dd>
</dl>
<small class="text-muted fst-italic">
<i class="fas fa-info-circle me-1"></i>
{{ t('ui.profile.credit_note', {}, 'common') }}
</small>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Netmail Addresses Modal -->
<div class="modal fade" id="netmailAddressesModal" tabindex="-1" aria-labelledby="netmailAddressesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="netmailAddressesModalLabel">
<i class="fas fa-address-card me-2"></i>
{{ t('ui.profile.netmail_addresses_title', {}, 'common') }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
{% if network_addresses|length > 0 %}
<div class="table-responsive">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>{{ t('ui.profile.netmail_col_network', {}, 'common') }}</th>
<th>{{ t('ui.profile.netmail_col_address', {}, 'common') }}</th>
</tr>
</thead>
<tbody>
{% for net in network_addresses %}
<tr>
<td>{{ net.domain }}</td>
<td class="font-monospace">{{ user_real_name }}@{{ net.address }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted p-3 mb-0">{{ t('ui.profile.no_networks_configured', {}, 'common') }}</p>
{% endif %}
</div>
{% if network_addresses|length > 0 %}
<div class="modal-footer">
<small class="text-muted"><i class="fas fa-info-circle me-1"></i>{{ t('ui.profile.netmail_username_note', {'username': user_username}, 'common') }}</small>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function uiT(key, fallback, params = {}) {
if (window.t) {
return window.t(key, params, fallback);
}
return fallback;
}
$(document).ready(function() {
loadActivityStats();
$('#profileForm').on('submit', function(e) {
e.preventDefault();
updateProfile();
});
});
function loadActivityStats() {
$.get('/api/user/stats')
.done(function(data) {
const netmail = data.netmail_count || 0;
const echomail = data.echomail_count || 0;
const dl = data.files_downloaded || 0;
const ul = data.files_uploaded || 0;
$('#netmailCount').text(netmail);
$('#echomailCount').text(echomail);
$('#totalCount').text(netmail + echomail);
$('#filesDownloaded').text(dl);
$('#filesUploaded').text(ul);
let ratio;
if (ul === 0 && dl === 0) {
ratio = uiT('ui.profile.ratio_na', 'N/A');
} else if (ul === 0) {
ratio = uiT('ui.profile.ratio_na', 'N/A');
} else {
ratio = (dl / ul).toFixed(2) + ':1';
}
$('#transferRatio').text(ratio);
})
.fail(function() {
$('#netmailCount, #echomailCount, #totalCount, #filesDownloaded, #filesUploaded, #transferRatio').text('?');
});
}
function updateProfile() {
const formData = new FormData($('#profileForm')[0]);
// Show loading status
$('#profileStatus').show().html(`
<div class="alert alert-info">
<i class="fas fa-spinner fa-spin me-2"></i>
${uiT('ui.profile.updating_profile', 'Updating profile...')}
</div>
`);
$.ajax({
url: '/api/user/profile',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
const successMessage = window.getApiMessage
? window.getApiMessage(response, uiT('ui.profile.updated_successfully', 'Profile updated successfully!'))
: uiT('ui.profile.updated_successfully', 'Profile updated successfully!');
$('#profileStatus').html(`
<div class="alert alert-success">
<i class="fas fa-check me-2"></i>
${escapeHtml(successMessage)}
</div>
`);
// Clear password fields
$('#currentPassword, #newPassword').val('');
// Update displayed name in navbar if it changed
if (response.real_name) {
// The navbar shows username, not real name, so no update needed
}
// Hide success message after 3 seconds
setTimeout(function() {
$('#profileStatus').fadeOut();
}, 3000);
},
error: function(xhr) {
const error = window.getApiErrorMessage
? window.getApiErrorMessage(xhr.responseJSON, uiT('errors.user.profile.update_failed', 'Failed to update profile'))
: uiT('errors.user.profile.update_failed', 'Failed to update profile');
$('#profileStatus').html(`
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
${escapeHtml(error)}
</div>
`);
}
});
}
</script>
{% endblock %}
|