25327e3302
[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.
138 lines
6.8 KiB
PHP
138 lines
6.8 KiB
PHP
<?php
|
|
require_once __DIR__.'/includes/boot.php';
|
|
requireLogin();
|
|
$pageTitle = 'Account Settings — Keyser WV';
|
|
$u = qone("SELECT * FROM users WHERE id=?",[uid()]);
|
|
$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.';
|
|
if ($pw !== $pw2) $errors[] = 'Passwords do not match.';
|
|
}
|
|
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 (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';
|
|
?>
|
|
<div class="form-box" style="max-width:520px">
|
|
<h1 class="form-title">Account Settings</h1>
|
|
<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" 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>
|
|
<div class="fg"><label class="flabel">NEW PASSWORD</label><input type="password" name="password" class="finput" autocomplete="new-password"></div>
|
|
<div class="fg"><label class="flabel">CONFIRM NEW PASSWORD</label><input type="password" name="password2" class="finput" autocomplete="new-password"></div>
|
|
<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'; ?>
|