- [index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/index.php): events now appear before featured businesses, plus forum pretty-route fallback.
[events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/events.php) and [admin/events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/events.php): event image uploads. [menu.php](/Users/tyemeclifford/Documents/GH/MyKeyser/menu.php), [admin/menus.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/menus.php), and [business.php](/Users/tyemeclifford/Documents/GH/MyKeyser/business.php): one configurable menu item image slot. [account.php](/Users/tyemeclifford/Documents/GH/MyKeyser/account.php): circular avatar upload and crop controls. [forum.php](/Users/tyemeclifford/Documents/GH/MyKeyser/forum.php), [admin/forum.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/forum.php), [.htaccess](/Users/tyemeclifford/Documents/GH/MyKeyser/.htaccess), and [forum/index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/forum/index.php): forum, categories, topic/reply uploads, permalinks, toggle/moderation. [includes/db.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/db.php) and [includes/functions.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/functions.php): schema upgrades and shared upload/image helpers. [assets/css/style.css](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/css/style.css): neon styling for forum, images, avatars, attachments.
This commit is contained in:
+95
-3
@@ -7,6 +7,7 @@ $errors = [];
|
||||
if (isPost()) {
|
||||
csrfCheck();
|
||||
$em = ps('email'); $pw = ps('password'); $pw2 = ps('password2');
|
||||
$avatarPath = (string)($u['avatar_path'] ?? '');
|
||||
if ($em && !filter_var($em,FILTER_VALIDATE_EMAIL)) $errors[] = 'Invalid email address.';
|
||||
if ($pw) {
|
||||
if (strlen($pw) < 6) $errors[] = 'Password must be at least 6 characters.';
|
||||
@@ -14,11 +15,18 @@ if (isPost()) {
|
||||
}
|
||||
if (!$errors && $em && $em !== $u['email'] && qval("SELECT id FROM users WHERE email=? AND id!=?",[$em,uid()])) $errors[] = 'Email already in use.';
|
||||
if (!$errors) {
|
||||
if ($pw) qrun("UPDATE users SET email=?,password=? WHERE id=?",[$em?:null,password_hash($pw,PASSWORD_BCRYPT),uid()]);
|
||||
else qrun("UPDATE users SET email=? WHERE id=?",[$em?:null,uid()]);
|
||||
if (p('remove_avatar','0') === '1') $avatarPath = '';
|
||||
$avatar = storeAvatarUpload('avatar', mbToBytes(setting('avatar_upload_max_mb','3'), 3));
|
||||
if (!$avatar['ok']) $errors[] = $avatar['error'];
|
||||
elseif (!empty($avatar['path'])) $avatarPath = (string)$avatar['path'];
|
||||
}
|
||||
if (!$errors) {
|
||||
if ($pw) qrun("UPDATE users SET email=?,avatar_path=?,password=? WHERE id=?",[$em?:null,$avatarPath,password_hash($pw,PASSWORD_BCRYPT),uid()]);
|
||||
else qrun("UPDATE users SET email=?,avatar_path=? WHERE id=?",[$em?:null,$avatarPath,uid()]);
|
||||
flash('Account updated successfully!','success'); go('/account.php');
|
||||
}
|
||||
$u['email'] = $em;
|
||||
$u['avatar_path'] = $avatarPath;
|
||||
}
|
||||
include __DIR__.'/includes/header.php';
|
||||
?>
|
||||
@@ -27,13 +35,35 @@ include __DIR__.'/includes/header.php';
|
||||
<p class="form-sub">Manage your Discover Keyser WV account</p>
|
||||
<div class="ibox" style="margin-bottom:1.5rem">
|
||||
<div class="ibox-title">ACCOUNT INFO</div>
|
||||
<div class="account-avatar-preview"><?=userAvatarHtml($u, 'avatar-lg')?></div>
|
||||
<div class="irow"><span class="irow-i">👤</span><span class="irow-v"><?=e($u['username'])?> <?=isAdmin()?'<span class="badge badge-gold">ADMIN</span>':''?></span></div>
|
||||
<div class="irow"><span class="irow-i">📅</span><span class="irow-v">Joined <?=fdate($u['created_at'])?></span></div>
|
||||
<?php if($u['last_login']): ?><div class="irow"><span class="irow-i">🕐</span><span class="irow-v">Last login: <?=ago($u['last_login'])?></span></div><?php endif; ?>
|
||||
</div>
|
||||
<?php if($errors): ?><div class="err-box"><?=implode('<br>',array_map('e',$errors))?></div><?php endif; ?>
|
||||
<form method="POST">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<?=csrfField()?>
|
||||
<div class="fg">
|
||||
<label class="flabel">PROFILE AVATAR</label>
|
||||
<div class="avatar-upload-grid">
|
||||
<canvas id="avatarCanvas" class="avatar-canvas" width="240" height="240" aria-label="Avatar crop preview"></canvas>
|
||||
<div>
|
||||
<input type="file" name="avatar" id="avatarInput" class="finput" accept="image/*">
|
||||
<div class="avatar-crop-controls" id="avatarCropControls">
|
||||
<label>Zoom <input type="range" id="avatarZoom" min="1" max="3" step="0.01" value="1"></label>
|
||||
<label>Horizontal <input type="range" id="avatarFocusX" min="0" max="100" value="50"></label>
|
||||
<label>Vertical <input type="range" id="avatarFocusY" min="0" max="100" value="50"></label>
|
||||
</div>
|
||||
<input type="hidden" name="avatar_crop_x" id="avatarCropX" value="0">
|
||||
<input type="hidden" name="avatar_crop_y" id="avatarCropY" value="0">
|
||||
<input type="hidden" name="avatar_crop_size" id="avatarCropSize" value="0">
|
||||
<div class="fhint">Optional JPG, PNG, GIF, or WebP. Max <?=e(setting('avatar_upload_max_mb','3'))?> MB.</div>
|
||||
<?php if(!empty($u['avatar_path'])): ?>
|
||||
<label style="display:flex;align-items:center;gap:.45rem;margin-top:.6rem;font-size:.85rem;color:var(--text-m);cursor:pointer"><input type="checkbox" name="remove_avatar" value="1"> Remove current avatar</label>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fg"><label class="flabel">EMAIL ADDRESS</label><input type="email" name="email" class="finput" value="<?=e($u['email']??'')?>"></div>
|
||||
<hr class="divider" style="margin:1.25rem 0">
|
||||
<p style="font-family:'Oswald',sans-serif;font-size:.72rem;letter-spacing:.1em;color:var(--gold);margin-bottom:.85rem">CHANGE PASSWORD (leave blank to keep current)</p>
|
||||
@@ -42,4 +72,66 @@ include __DIR__.'/includes/header.php';
|
||||
<button type="submit" class="btn btn-primary btn-block">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php $footExtra = <<<'HTML'
|
||||
<script>
|
||||
(function(){
|
||||
const input = document.getElementById('avatarInput');
|
||||
const canvas = document.getElementById('avatarCanvas');
|
||||
if (!input || !canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const zoom = document.getElementById('avatarZoom');
|
||||
const fx = document.getElementById('avatarFocusX');
|
||||
const fy = document.getElementById('avatarFocusY');
|
||||
const cx = document.getElementById('avatarCropX');
|
||||
const cy = document.getElementById('avatarCropY');
|
||||
const cs = document.getElementById('avatarCropSize');
|
||||
let img = null;
|
||||
|
||||
function drawEmpty(){
|
||||
ctx.clearRect(0,0,240,240);
|
||||
ctx.fillStyle = '#121f38';
|
||||
ctx.fillRect(0,0,240,240);
|
||||
ctx.strokeStyle = 'rgba(38,244,255,.35)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(120,120,104,0,Math.PI*2);
|
||||
ctx.stroke();
|
||||
}
|
||||
function redraw(){
|
||||
if (!img) { drawEmpty(); return; }
|
||||
const z = parseFloat(zoom.value || '1');
|
||||
const natural = Math.min(img.naturalWidth, img.naturalHeight);
|
||||
const size = Math.max(1, Math.round(natural / z));
|
||||
const x = Math.round((img.naturalWidth - size) * (parseInt(fx.value,10) / 100));
|
||||
const y = Math.round((img.naturalHeight - size) * (parseInt(fy.value,10) / 100));
|
||||
cx.value = x; cy.value = y; cs.value = size;
|
||||
ctx.clearRect(0,0,240,240);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(120,120,118,0,Math.PI*2);
|
||||
ctx.clip();
|
||||
ctx.drawImage(img, x, y, size, size, 0, 0, 240, 240);
|
||||
ctx.restore();
|
||||
ctx.strokeStyle = 'rgba(255,209,102,.9)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(120,120,118,0,Math.PI*2);
|
||||
ctx.stroke();
|
||||
}
|
||||
input.addEventListener('change', function(){
|
||||
const file = input.files && input.files[0];
|
||||
if (!file) { img = null; redraw(); return; }
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(){
|
||||
img = new Image();
|
||||
img.onload = redraw;
|
||||
img.src = reader.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
[zoom, fx, fy].forEach(el => el && el.addEventListener('input', redraw));
|
||||
drawEmpty();
|
||||
})();
|
||||
</script>
|
||||
HTML; ?>
|
||||
<?php include __DIR__.'/includes/footer.php'; ?>
|
||||
|
||||
Reference in New Issue
Block a user