Redesigns profile page

main
Yasen Pramatarov 2025-11-27 12:44:15 +02:00
parent 969875460f
commit 1252c421bc
3 changed files with 324 additions and 81 deletions

View File

@ -14,6 +14,19 @@
$action = $_REQUEST['action'] ?? ''; $action = $_REQUEST['action'] ?? '';
$item = $_REQUEST['item'] ?? ''; $item = $_REQUEST['item'] ?? '';
$subscription = null;
if (defined('PLUGIN_BILLING_PATH') && file_exists(PLUGIN_BILLING_PATH . 'models/billing.php')) {
require_once PLUGIN_BILLING_PATH . 'models/billing.php';
if (class_exists('Billing')) {
try {
$billingModel = new Billing($db);
$subscription = $billingModel->getUserSubscription($userId);
} catch (Throwable $e) {
error_log('Failed to load billing subscription: ' . $e->getMessage());
}
}
}
// if a form is submitted, it's from the edit page // if a form is submitted, it's from the edit page
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {

View File

@ -1,87 +1,126 @@
<!-- user profile --> <?php
<div class="card text-center w-50 mx-auto"> $user = $userDetails[0] ?? [];
$username = $user['username'] ?? '';
$name = $user['name'] ?? '';
$email = $user['email'] ?? '';
$timezoneName = $user['timezone'] ?? '';
$timezoneOffset = $timezoneName ? getUTCOffset($timezoneName) : '';
$bio = trim($user['bio'] ?? '');
$rightsNames = array_map(function ($right) {
return trim($right['right_name'] ?? '');
}, $userRights);
$rightsNames = array_filter($rightsNames, function ($label) {
return $label !== '';
});
$rightsCount = count($rightsNames);
$displayName = $name ?: $username ?: 'User profile';
$timezoneDisplay = '';
if ($timezoneName) {
if ($timezoneOffset !== '') {
$offsetLabel = stripos($timezoneOffset, 'UTC') === 0 ? $timezoneOffset : 'UTC' . $timezoneOffset;
$timezoneDisplay = sprintf('%s (%s)', $timezoneName, $offsetLabel);
} else {
$timezoneDisplay = $timezoneName;
}
}
<p class="h4 card-header">Profile of <?= htmlspecialchars($userDetails[0]['username']) ?></p> ?>
<div class="card-body">
<div class="row"> <section class="tm-directory tm-profile-view">
<div class="tm-hero-card tm-hero-card--stacked tm-profile-hero">
<div class="col-md-4 avatar-container"> <div class="tm-profile-hero-main">
<div> <div class="tm-profile-avatar-frame">
<img class="avatar-img" src="<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>" alt="avatar" /> <img src="<?= htmlspecialchars($app_root) . htmlspecialchars($avatar) ?>" alt="Avatar of <?= htmlspecialchars($displayName) ?>" />
</div>
<div class="tm-profile-hero-body">
<h1 class="tm-profile-title"><?= htmlspecialchars($displayName) ?></h1>
<p class="tm-profile-subtitle">Personal details and access summary for this TotalMeet account.</p>
<div class="tm-profile-hero-meta">
<?php if ($username): ?>
<span class="tm-hero-pill pill-neutral">
<i class="fas fa-user"></i>
@<?= htmlspecialchars($username) ?>
</span>
<?php endif; ?>
<?php if ($timezoneDisplay): ?>
<span class="tm-hero-pill pill-primary">
<i class="fas fa-clock"></i>
<?= htmlspecialchars($timezoneDisplay) ?>
</span>
<?php endif; ?>
<span class="tm-hero-pill pill-accent">
<i class="fas fa-shield-alt"></i>
<?= $rightsCount ?> <?= $rightsCount === 1 ? 'Right' : 'Rights' ?>
</span>
</div> </div>
</div> </div>
<div class="tm-profile-hero-actions">
<div class="col-md-8"> <a class="btn btn-primary" href="<?= htmlspecialchars($app_root) ?>?page=profile&amp;action=edit">
<i class="fas fa-edit"></i> Edit profile
<!--div class="row mb-3"> </a>
<div class="col-md-4 text-end">
<label class="form-label"><small>username:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['username']) ?>
</div>
</div-->
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>name:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['name'] ?? '') ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>email:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?= htmlspecialchars($userDetails[0]['email'] ?? '') ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>timezone:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?php if (!empty($userDetails[0]['timezone'])) { ?>
<?= htmlspecialchars($userDetails[0]['timezone']) ?>&nbsp;&nbsp;<span style="font-size: 0.66em;">(<?= htmlspecialchars(getUTCOffset($userDetails[0]['timezone'])) ?>)</span>
<?php } ?>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>bio:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<textarea class="scroll-box" rows="10" readonly><?= htmlspecialchars($userDetails[0]['bio'] ?? '') ?></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 text-end">
<label class="form-label"><small>rights:</small></label>
</div>
<div class="col-md-8 text-start bg-light">
<?php foreach ($userRights as $right) { ?>
<?= htmlspecialchars($right['right_name'] ?? '') ?>
<br />
<?php } ?>
</div>
</div>
</div> </div>
<p>
<a href="<?= htmlspecialchars($app_root) ?>?page=profile&action=edit" class="btn btn-primary">Edit</a>
</p>
</div> </div>
</div> </div>
</div>
<!-- /user profile --> <div class="tm-profile-panels">
<article class="tm-profile-panel">
<header>
<h3>Account details</h3>
</header>
<dl class="tm-profile-detail-list">
<div class="tm-profile-detail-item">
<dt>Full name</dt>
<dd><?= $name ? htmlspecialchars($name) : '<span class="tm-profile-placeholder">Not provided</span>' ?></dd>
</div>
<div class="tm-profile-detail-item">
<dt>Email</dt>
<dd><?= $email ? htmlspecialchars($email) : '<span class="tm-profile-placeholder">Not provided</span>' ?></dd>
</div>
<div class="tm-profile-detail-item">
<dt>Username</dt>
<dd><?= $username ? htmlspecialchars($username) : '<span class="tm-profile-placeholder">Not provided</span>' ?></dd>
</div>
<div class="tm-profile-detail-item">
<dt>Timezone</dt>
<dd><?= $timezoneDisplay ? htmlspecialchars($timezoneDisplay) : '<span class="tm-profile-placeholder">Not set</span>' ?></dd>
</div>
</dl>
</article>
<article class="tm-profile-panel">
<header>
<h3>Bio</h3>
</header>
<?php if ($bio !== ''): ?>
<p class="tm-profile-bio"><?= nl2br(htmlspecialchars($bio)) ?></p>
<?php else: ?>
<p class="tm-profile-placeholder">This user hasnt added a bio yet.</p>
<?php endif; ?>
</article>
<article class="tm-profile-panel">
<header>
<h3>User rights</h3>
</header>
<?php if ($rightsCount): ?>
<ul class="tm-profile-rights">
<?php foreach ($rightsNames as $rightLabel): ?>
<li>
<i class="fas fa-check"></i>
<?= htmlspecialchars($rightLabel) ?>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="tm-profile-placeholder">No rights assigned yet.</p>
<?php endif; ?>
</article>
<?php do_hook('profile.additional_panels', [
'subscription' => $subscription ?? null,
'app_root' => $app_root,
'userId' => $user['id'] ?? null,
]); ?>
</div>
</section>

View File

@ -1,5 +1,194 @@
.tm-profile-view {
display: flex;
flex-direction: column;
/* gap: 2rem;*/
margin-bottom: 3rem;
}
.tm-profile-hero {
padding: 2rem;
}
.tm-profile-hero-main {
display: flex;
align-items: center;
gap: 2rem;
flex-wrap: wrap;
}
.tm-profile-avatar-frame {
width: 140px;
height: 140px;
border-radius: 0.5rem;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(14, 165, 233, 0.15));
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.45);
}
.tm-profile-avatar-frame img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 1.2rem;
}
.tm-profile-hero-body {
flex: 1;
min-width: 240px;
}
.tm-profile-hero-meta {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1rem;
}
.tm-profile-hero-actions {
margin-left: auto;
}
.tm-profile-panels {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
.tm-profile-panel {
background: rgba(255, 255, 255, 0.98);
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 1.25rem;
padding: 1.75rem;
box-shadow: 0 20px 35px rgba(15, 23, 42, 0.05);
display: flex;
flex-direction: column;
gap: 1rem;
}
.tm-profile-panel h3 {
margin: 0;
color: #0f172a;
font-size: 1.25rem;
}
.tm-profile-detail-list {
margin: 0;
padding: 0;
display: grid;
gap: 1rem;
}
.tm-profile-detail-item dt {
margin: 0;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: #94a3b8;
}
.tm-profile-detail-item dd {
margin: 0.35rem 0 0;
font-size: 1rem;
color: #0f172a;
word-break: break-word;
overflow-wrap: anywhere;
}
.tm-profile-bio {
margin: 0;
color: #475569;
line-height: 1.6;
white-space: pre-line;
}
.tm-profile-rights {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.tm-profile-rights li {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
color: #0f172a;
}
.tm-profile-rights i {
color: #22c55e;
}
.tm-profile-placeholder {
color: #94a3b8;
font-style: italic;
}
.tm-profile-billing header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.tm-profile-plan-pill {
padding: 0.2rem 0.85rem;
border-radius: 999px;
border: 1px solid rgba(37, 99, 235, 0.35);
color: #1d4ed8;
font-size: 0.85rem;
font-weight: 600;
}
.tm-profile-plan-desc {
margin: 0;
color: #475569;
}
.tm-profile-plan-meta {
margin: 1rem 0 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1rem;
}
.tm-profile-plan-meta dt {
margin: 0;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: #94a3b8;
}
.tm-profile-plan-meta dd {
margin: 0.35rem 0 0;
font-weight: 600;
color: #0f172a;
}
@media (max-width: 768px) {
.tm-profile-hero-actions {
width: 100%;
}
.tm-profile-hero-body {
order: 2;
}
.tm-profile-avatar-frame {
order: 1;
}
}
.tm-theme-gallery { .tm-theme-gallery {
padding: 2rem 0; padding: 0;
} }
.tm-theme-header { .tm-theme-header {
@ -226,7 +415,7 @@ html, body {
.tm-hero { .tm-hero {
/* max-width: 1650px;*/ /* max-width: 1650px;*/
width: 100%; width: 100%;
margin: 0 auto 2.5rem; /* margin: 0 auto 2.5rem;*/
/* padding: 0 1rem;*/ /* padding: 0 1rem;*/
} }
@ -3181,6 +3370,7 @@ body {
transition: width 0.5s ease; transition: width 0.5s ease;
/* margin-bottom: 50px;*/ /* margin-bottom: 50px;*/
padding: 2rem; padding: 2rem;
padding-left: 0.5rem;
/* width: 80%;*/ /* width: 80%;*/
} }
.main-content.expanded { .main-content.expanded {
@ -3188,6 +3378,7 @@ body {
} }
.sidebar-collapsed .main-content { .sidebar-collapsed .main-content {
width: calc(70% + 3em); width: calc(70% + 3em);
padding-left: 1rem;
} }
.sidebar-content a { .sidebar-content a {