- v1.1.0 - Redesign
This commit is contained in:
@@ -6,6 +6,7 @@ $pageTitle = ($adminTitle ?? 'Admin') . ' — Keyser WV Admin';
|
|||||||
include dirname(__DIR__).'/includes/header.php';
|
include dirname(__DIR__).'/includes/header.php';
|
||||||
// Pending owner edit count for badge
|
// Pending owner edit count for badge
|
||||||
$_pendingEdits = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'");
|
$_pendingEdits = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'");
|
||||||
|
$_pendingClaims = (int)qval("SELECT COUNT(*) FROM business_claim_requests WHERE status='pending'");
|
||||||
?>
|
?>
|
||||||
<div class="admin-wrap">
|
<div class="admin-wrap">
|
||||||
<aside class="admin-side">
|
<aside class="admin-side">
|
||||||
@@ -29,7 +30,12 @@ $_pendingEdits = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE st
|
|||||||
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
|
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
|
||||||
<div class="admin-side-title" style="margin-top:1rem">USERS</div>
|
<div class="admin-side-title" style="margin-top:1rem">USERS</div>
|
||||||
<a class="asl" href="/admin/users.php">👥 Users</a>
|
<a class="asl" href="/admin/users.php">👥 Users</a>
|
||||||
<a class="asl" href="/admin/owners.php">🔑 Assign Owners</a>
|
<a class="asl" href="/admin/owners.php" style="display:flex;align-items:center;justify-content:space-between">
|
||||||
|
<span>🔑 Assign Owners</span>
|
||||||
|
<?php if ($_pendingClaims): ?>
|
||||||
|
<span style="background:var(--gold);color:var(--bg0);font-family:'Oswald',sans-serif;font-size:.65rem;padding:.1rem .42rem;border-radius:10px;font-weight:600"><?=$_pendingClaims?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</a>
|
||||||
<div class="admin-side-title" style="margin-top:1rem">SETTINGS</div>
|
<div class="admin-side-title" style="margin-top:1rem">SETTINGS</div>
|
||||||
<a class="asl" href="/admin/settings.php">⚙️ Site Settings</a>
|
<a class="asl" href="/admin/settings.php">⚙️ Site Settings</a>
|
||||||
<hr style="border-color:var(--bdr);margin:1rem 0">
|
<hr style="border-color:var(--bdr);margin:1rem 0">
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ $fieldLabels = [
|
|||||||
'address' => 'Address',
|
'address' => 'Address',
|
||||||
'phone' => 'Phone',
|
'phone' => 'Phone',
|
||||||
'email' => 'Email',
|
'email' => 'Email',
|
||||||
|
'website' => 'Website',
|
||||||
|
'video_url' => 'Video URL',
|
||||||
'hours' => 'Hours of Operation',
|
'hours' => 'Hours of Operation',
|
||||||
'is_active' => 'Listing Status (Open/Closed)',
|
'is_active' => 'Listing Status (Open/Closed)',
|
||||||
];
|
];
|
||||||
@@ -34,6 +36,10 @@ if (isPost()) {
|
|||||||
if (is_array($decoded)) {
|
if (is_array($decoded)) {
|
||||||
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $req['business_id']]);
|
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $req['business_id']]);
|
||||||
}
|
}
|
||||||
|
} elseif ($field === 'video_url') {
|
||||||
|
if (validBusinessVideoUrl($val)) {
|
||||||
|
qrun("UPDATE businesses SET video_url=? WHERE id=?", [$val, $req['business_id']]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $req['business_id']]);
|
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $req['business_id']]);
|
||||||
}
|
}
|
||||||
@@ -70,6 +76,10 @@ if (isPost()) {
|
|||||||
if (is_array($decoded)) {
|
if (is_array($decoded)) {
|
||||||
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $bizId]);
|
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $bizId]);
|
||||||
}
|
}
|
||||||
|
} elseif ($field === 'video_url') {
|
||||||
|
if (validBusinessVideoUrl($val)) {
|
||||||
|
qrun("UPDATE businesses SET video_url=? WHERE id=?", [$val, $bizId]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $bizId]);
|
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $bizId]);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-2
@@ -40,6 +40,7 @@ if (isPost()) {
|
|||||||
'phone' => ps('phone'),
|
'phone' => ps('phone'),
|
||||||
'email' => ps('email'),
|
'email' => ps('email'),
|
||||||
'website' => ps('website'),
|
'website' => ps('website'),
|
||||||
|
'video_url' => ps('video_url'),
|
||||||
'hours' => json_encode($hrs),
|
'hours' => json_encode($hrs),
|
||||||
'lat' => (float)p('lat', 39.4364),
|
'lat' => (float)p('lat', 39.4364),
|
||||||
'lng' => (float)p('lng', -78.9762),
|
'lng' => (float)p('lng', -78.9762),
|
||||||
@@ -47,12 +48,17 @@ if (isPost()) {
|
|||||||
'is_featured' => (int)p('is_featured', 0),
|
'is_featured' => (int)p('is_featured', 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (!validBusinessVideoUrl($fields['video_url'])) {
|
||||||
|
flash('Business videos must be a YouTube or Vimeo URL.','error');
|
||||||
|
go('/admin/businesses.php'.($id?"?edit=$id":''));
|
||||||
|
}
|
||||||
|
|
||||||
if ($act === 'add') {
|
if ($act === 'add') {
|
||||||
qrun("INSERT INTO businesses(name,slug,category_id,subcategory,description,address,phone,email,website,hours,lat,lng,is_active,is_featured)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)", array_values($fields));
|
qrun("INSERT INTO businesses(name,slug,category_id,subcategory,description,address,phone,email,website,video_url,hours,lat,lng,is_active,is_featured)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", array_values($fields));
|
||||||
flash('Business added!','success');
|
flash('Business added!','success');
|
||||||
go('/admin/businesses.php');
|
go('/admin/businesses.php');
|
||||||
} else {
|
} else {
|
||||||
qrun("UPDATE businesses SET name=?,slug=?,category_id=?,subcategory=?,description=?,address=?,phone=?,email=?,website=?,hours=?,lat=?,lng=?,is_active=?,is_featured=? WHERE id=?", array_merge(array_values($fields), [$id]));
|
qrun("UPDATE businesses SET name=?,slug=?,category_id=?,subcategory=?,description=?,address=?,phone=?,email=?,website=?,video_url=?,hours=?,lat=?,lng=?,is_active=?,is_featured=? WHERE id=?", array_merge(array_values($fields), [$id]));
|
||||||
flash('Business updated!','success');
|
flash('Business updated!','success');
|
||||||
go('/admin/businesses.php?edit='.$id);
|
go('/admin/businesses.php?edit='.$id);
|
||||||
}
|
}
|
||||||
@@ -152,6 +158,12 @@ $bizs = qall("SELECT b.*,c.name cat FROM businesses b LEFT JOIN categories c
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel">VIDEO URL</label>
|
||||||
|
<input type="url" name="video_url" class="finput" value="<?=e($editing['video_url']??'')?>" placeholder="YouTube or Vimeo URL">
|
||||||
|
<div class="fhint">Optional. Business listing videos are rendered through lone-embed.php and should use YouTube or Vimeo.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="fg">
|
<div class="fg">
|
||||||
<label class="flabel">LATITUDE</label>
|
<label class="flabel">LATITUDE</label>
|
||||||
|
|||||||
+40
-2
@@ -7,6 +7,7 @@ $stats = [
|
|||||||
'Reviews' => qval("SELECT COUNT(*) FROM reviews"),
|
'Reviews' => qval("SELECT COUNT(*) FROM reviews"),
|
||||||
'Events' => qval("SELECT COUNT(*) FROM events WHERE status='approved'"),
|
'Events' => qval("SELECT COUNT(*) FROM events WHERE status='approved'"),
|
||||||
'Pending Events' => qval("SELECT COUNT(*) FROM events WHERE status='pending'"),
|
'Pending Events' => qval("SELECT COUNT(*) FROM events WHERE status='pending'"),
|
||||||
|
'Pending Claims' => qval("SELECT COUNT(*) FROM business_claim_requests WHERE status='pending'"),
|
||||||
'Pending Edits' => qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'"),
|
'Pending Edits' => qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'"),
|
||||||
'Open Reports' => qval("SELECT COUNT(*) FROM reports WHERE status='open'"),
|
'Open Reports' => qval("SELECT COUNT(*) FROM reports WHERE status='open'"),
|
||||||
'Active Alerts' => qval("SELECT COUNT(*) FROM alerts WHERE is_active=1"),
|
'Active Alerts' => qval("SELECT COUNT(*) FROM alerts WHERE is_active=1"),
|
||||||
@@ -15,6 +16,15 @@ $recentBiz = qall("SELECT * FROM businesses ORDER BY created_at DESC LIMIT 5"
|
|||||||
$recentUsers = qall("SELECT * FROM users ORDER BY created_at DESC LIMIT 5");
|
$recentUsers = qall("SELECT * FROM users ORDER BY created_at DESC LIMIT 5");
|
||||||
$pendingEvts = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by WHERE e.status='pending' ORDER BY e.created_at DESC LIMIT 5");
|
$pendingEvts = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by WHERE e.status='pending' ORDER BY e.created_at DESC LIMIT 5");
|
||||||
$openReports = qall("SELECT r.*,b.name biz,u.username reporter FROM reports r JOIN businesses b ON b.id=r.business_id JOIN users u ON u.id=r.user_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 5");
|
$openReports = qall("SELECT r.*,b.name biz,u.username reporter FROM reports r JOIN businesses b ON b.id=r.business_id JOIN users u ON u.id=r.user_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 5");
|
||||||
|
$pendingClaims = qall("
|
||||||
|
SELECT bcr.*, b.name biz_name, b.slug biz_slug, u.username, u.email
|
||||||
|
FROM business_claim_requests bcr
|
||||||
|
JOIN businesses b ON b.id = bcr.business_id
|
||||||
|
JOIN users u ON u.id = bcr.user_id
|
||||||
|
WHERE bcr.status = 'pending'
|
||||||
|
ORDER BY bcr.created_at DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
$pendingEdits = qall("
|
$pendingEdits = qall("
|
||||||
SELECT ber.*, b.name biz_name, b.slug biz_slug, b.id biz_id, u.username
|
SELECT ber.*, b.name biz_name, b.slug biz_slug, b.id biz_id, u.username
|
||||||
FROM business_edit_requests ber
|
FROM business_edit_requests ber
|
||||||
@@ -30,7 +40,7 @@ $pendingEdits = qall("
|
|||||||
<div class="stat-cards">
|
<div class="stat-cards">
|
||||||
<?php foreach($stats as $lbl=>$val): ?>
|
<?php foreach($stats as $lbl=>$val): ?>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="sc-n" style="<?=$lbl==='Pending Edits'&&$val>0?'color:var(--gold)':($lbl==='Open Reports'&&$val>0?'color:var(--red-l)':'')?>">
|
<div class="sc-n" style="<?=($lbl==='Pending Edits'||$lbl==='Pending Claims')&&$val>0?'color:var(--gold)':($lbl==='Open Reports'&&$val>0?'color:var(--red-l)':'')?>">
|
||||||
<?=$val?>
|
<?=$val?>
|
||||||
</div>
|
</div>
|
||||||
<div class="sc-l"><?=e($lbl)?></div>
|
<div class="sc-l"><?=e($lbl)?></div>
|
||||||
@@ -38,6 +48,34 @@ $pendingEdits = qall("
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pending owner claims — verification queue -->
|
||||||
|
<?php if ($pendingClaims): ?>
|
||||||
|
<div style="margin-bottom:2rem">
|
||||||
|
<div class="admin-sec-title" style="color:var(--gold)">
|
||||||
|
🔑 BUSINESS PAGE CLAIM REQUESTS (<?=count($pendingClaims)?>)
|
||||||
|
</div>
|
||||||
|
<div class="tbl-wrap">
|
||||||
|
<table class="dtbl">
|
||||||
|
<thead><tr><th>BUSINESS</th><th>REQUESTED BY</th><th>CONTACT PHONE</th><th>SUBMITTED</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($pendingClaims as $claim): ?>
|
||||||
|
<tr>
|
||||||
|
<td><a href="/business.php?slug=<?=e($claim['biz_slug'])?>" target="_blank" class="td-name"><?=e($claim['biz_name'])?></a></td>
|
||||||
|
<td>
|
||||||
|
<div class="td-name"><?=e($claim['username'])?></div>
|
||||||
|
<?php if ($claim['email']): ?><div style="font-size:.76rem;color:var(--text-d)"><?=e($claim['email'])?></div><?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td style="font-size:.86rem;color:var(--text)"><?=e($claim['contact_phone'])?></td>
|
||||||
|
<td style="font-size:.78rem;color:var(--text-d)"><?=ago($claim['created_at'] ?? '')?></td>
|
||||||
|
<td><a href="/admin/owners.php#claims" class="btn btn-xs btn-primary">Verify</a></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Pending owner edits — top priority -->
|
<!-- Pending owner edits — top priority -->
|
||||||
<?php if ($pendingEdits): ?>
|
<?php if ($pendingEdits): ?>
|
||||||
<div style="margin-bottom:2rem">
|
<div style="margin-bottom:2rem">
|
||||||
@@ -50,7 +88,7 @@ $pendingEdits = qall("
|
|||||||
<tbody>
|
<tbody>
|
||||||
<?php
|
<?php
|
||||||
$fieldLabels = ['description'=>'Description','address'=>'Address','phone'=>'Phone',
|
$fieldLabels = ['description'=>'Description','address'=>'Address','phone'=>'Phone',
|
||||||
'email'=>'Email','hours'=>'Hours','is_active'=>'Status'];
|
'email'=>'Email','website'=>'Website','video_url'=>'Video','hours'=>'Hours','is_active'=>'Status'];
|
||||||
foreach ($pendingEdits as $pe):
|
foreach ($pendingEdits as $pe):
|
||||||
$preview = $pe['field'] === 'hours'
|
$preview = $pe['field'] === 'hours'
|
||||||
? '(hours update)'
|
? '(hours update)'
|
||||||
|
|||||||
@@ -16,6 +16,23 @@ if (isPost()) {
|
|||||||
qrun("DELETE FROM business_owners WHERE user_id=? AND business_id=?",[(int)p('user_id'),(int)p('business_id')]);
|
qrun("DELETE FROM business_owners WHERE user_id=? AND business_id=?",[(int)p('user_id'),(int)p('business_id')]);
|
||||||
flash('Owner removed.','success');
|
flash('Owner removed.','success');
|
||||||
}
|
}
|
||||||
|
if ($act === 'approve_claim') {
|
||||||
|
$claimId = (int)p('claim_id');
|
||||||
|
$claim = qone("SELECT * FROM business_claim_requests WHERE id=? AND status='pending'", [$claimId]);
|
||||||
|
if ($claim) {
|
||||||
|
qrun("INSERT OR IGNORE INTO business_owners(user_id,business_id)VALUES(?,?)", [(int)$claim['user_id'], (int)$claim['business_id']]);
|
||||||
|
qrun("UPDATE business_claim_requests SET status='approved', reviewed_at=datetime('now') WHERE id=?", [$claimId]);
|
||||||
|
flash('Business claim approved and owner assigned.', 'success');
|
||||||
|
}
|
||||||
|
go('/admin/owners.php#claims');
|
||||||
|
}
|
||||||
|
if ($act === 'reject_claim') {
|
||||||
|
$claimId = (int)p('claim_id');
|
||||||
|
qrun("UPDATE business_claim_requests SET status='rejected', admin_notes=?, reviewed_at=datetime('now') WHERE id=? AND status='pending'",
|
||||||
|
[ps('admin_notes'), $claimId]);
|
||||||
|
flash('Business claim rejected.', 'info');
|
||||||
|
go('/admin/owners.php#claims');
|
||||||
|
}
|
||||||
go('/admin/owners.php'.($_GET?'?'.http_build_query($_GET):''));
|
go('/admin/owners.php'.($_GET?'?'.http_build_query($_GET):''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,9 +41,66 @@ $filterBiz = iget('business_id');
|
|||||||
$users = qall("SELECT id,username FROM users WHERE is_active=1 ORDER BY username");
|
$users = qall("SELECT id,username FROM users WHERE is_active=1 ORDER BY username");
|
||||||
$bizs = qall("SELECT id,name FROM businesses WHERE is_active=1 ORDER BY name");
|
$bizs = qall("SELECT id,name FROM businesses WHERE is_active=1 ORDER BY name");
|
||||||
$owners = qall("SELECT bo.*,u.username,b.name biz_name FROM business_owners bo JOIN users u ON u.id=bo.user_id JOIN businesses b ON b.id=bo.business_id ORDER BY b.name,u.username");
|
$owners = qall("SELECT bo.*,u.username,b.name biz_name FROM business_owners bo JOIN users u ON u.id=bo.user_id JOIN businesses b ON b.id=bo.business_id ORDER BY b.name,u.username");
|
||||||
|
$claims = qall("
|
||||||
|
SELECT bcr.*, u.username, u.email, b.name biz_name, b.slug biz_slug
|
||||||
|
FROM business_claim_requests bcr
|
||||||
|
JOIN users u ON u.id=bcr.user_id
|
||||||
|
JOIN businesses b ON b.id=bcr.business_id
|
||||||
|
WHERE bcr.status='pending'
|
||||||
|
ORDER BY bcr.created_at DESC
|
||||||
|
");
|
||||||
?>
|
?>
|
||||||
<?php adminTitle('Assign Business Owners','Link user accounts to businesses so they can manage listings, post alerts, and update menus.') ?>
|
<?php adminTitle('Assign Business Owners','Link user accounts to businesses so they can manage listings, post alerts, and update menus.') ?>
|
||||||
|
|
||||||
|
<div id="claims" class="admin-sec-title">PENDING BUSINESS CLAIMS (<?=count($claims)?>)</div>
|
||||||
|
<?php if($claims): ?>
|
||||||
|
<div class="tbl-wrap" style="margin-bottom:2rem">
|
||||||
|
<table class="dtbl">
|
||||||
|
<thead><tr><th>BUSINESS</th><th>REQUESTED BY</th><th>CONTACT PHONE</th><th>PENDING EDITS</th><th>SUBMITTED</th><th>ACTIONS</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach($claims as $claim): ?>
|
||||||
|
<?php $editCount = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE business_id=? AND user_id=? AND status='pending'", [$claim['business_id'], $claim['user_id']]); ?>
|
||||||
|
<tr>
|
||||||
|
<td><a href="/business.php?slug=<?=e($claim['biz_slug'])?>" target="_blank" class="td-name"><?=e($claim['biz_name'])?></a></td>
|
||||||
|
<td>
|
||||||
|
<div class="td-name"><?=e($claim['username'])?></div>
|
||||||
|
<?php if($claim['email']): ?><div style="font-size:.76rem;color:var(--text-d)"><?=e($claim['email'])?></div><?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td style="color:var(--text)"><?=e($claim['contact_phone'])?></td>
|
||||||
|
<td>
|
||||||
|
<?php if($editCount): ?>
|
||||||
|
<a href="/admin/biz_edits.php?biz_id=<?=$claim['business_id']?>" class="badge badge-gold"><?=$editCount?> edit<?=$editCount!==1?'s':''?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span style="color:var(--text-d)">None</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td style="font-size:.78rem;color:var(--text-d)"><?=ago($claim['created_at'] ?? '')?></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex-row" style="gap:.35rem">
|
||||||
|
<form method="POST" style="display:inline">
|
||||||
|
<?=csrfField()?>
|
||||||
|
<input type="hidden" name="_act" value="approve_claim">
|
||||||
|
<input type="hidden" name="claim_id" value="<?=$claim['id']?>">
|
||||||
|
<button class="btn btn-xs btn-success" data-confirm="Approve this claim and assign <?=e($claim['username'])?> as owner of <?=e($claim['biz_name'])?>?">Approve</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" style="display:flex;gap:.35rem;align-items:center">
|
||||||
|
<?=csrfField()?>
|
||||||
|
<input type="hidden" name="_act" value="reject_claim">
|
||||||
|
<input type="hidden" name="claim_id" value="<?=$claim['id']?>">
|
||||||
|
<input type="text" name="admin_notes" class="finput" style="width:150px;font-size:.75rem;padding:.25rem .45rem" placeholder="Reason">
|
||||||
|
<button class="btn btn-xs btn-danger">Reject</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="empty" style="padding:1.75rem;margin-bottom:2rem"><div class="empty-ico">🔑</div><h3>No pending business claims</h3></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
|
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
|
||||||
<div class="ibox-title">🔑 ASSIGN OWNER TO BUSINESS</div>
|
<div class="ibox-title">🔑 ASSIGN OWNER TO BUSINESS</div>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ if (isPost()) {
|
|||||||
setSetting('contact_email', ps('contact_email'));
|
setSetting('contact_email', ps('contact_email'));
|
||||||
setSetting('registration_enabled',p('registration_enabled','0'));
|
setSetting('registration_enabled',p('registration_enabled','0'));
|
||||||
setSetting('events_require_approval',p('events_require_approval','0'));
|
setSetting('events_require_approval',p('events_require_approval','0'));
|
||||||
|
setSetting('home_random_interval',p('home_random_interval','2hours') === 'day' ? 'day' : '2hours');
|
||||||
flash('Settings saved!','success');
|
flash('Settings saved!','success');
|
||||||
go('/admin/settings.php');
|
go('/admin/settings.php');
|
||||||
}
|
}
|
||||||
@@ -33,6 +34,7 @@ $evtApproval = setting('events_require_approval','1');
|
|||||||
$siteName = setting('site_name','Discover Keyser WV');
|
$siteName = setting('site_name','Discover Keyser WV');
|
||||||
$siteTagline = setting('site_tagline','The Friendliest City in the U.S.A.');
|
$siteTagline = setting('site_tagline','The Friendliest City in the U.S.A.');
|
||||||
$contactEmail = setting('contact_email','info@discoverkeyser.com');
|
$contactEmail = setting('contact_email','info@discoverkeyser.com');
|
||||||
|
$homeRandomInterval = setting('home_random_interval','2hours');
|
||||||
$me = qone("SELECT * FROM users WHERE id=?",[uid()]);
|
$me = qone("SELECT * FROM users WHERE id=?",[uid()]);
|
||||||
?>
|
?>
|
||||||
<?php adminTitle('Site Settings') ?>
|
<?php adminTitle('Site Settings') ?>
|
||||||
@@ -79,6 +81,20 @@ $me = qone("SELECT * FROM users WHERE id=?",[uid()]);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel">HOMEPAGE RANDOM BUSINESS ROTATION</label>
|
||||||
|
<div style="margin-top:.4rem">
|
||||||
|
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem;margin-bottom:.35rem">
|
||||||
|
<input type="radio" name="home_random_interval" value="2hours"<?=$homeRandomInterval==='2hours'?' checked':''?>>
|
||||||
|
<span>Refresh the random list every two hours</span>
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem">
|
||||||
|
<input type="radio" name="home_random_interval" value="day"<?=$homeRandomInterval==='day'?' checked':''?>>
|
||||||
|
<span>Refresh the random list once per day</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Save Settings</button>
|
<button type="submit" class="btn btn-primary">Save Settings</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+120
-34
@@ -1,21 +1,23 @@
|
|||||||
/* ═══════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════
|
||||||
DISCOVER KEYSER WV — Dark West Virginia Theme
|
DISCOVER KEYSER WV — Neon Mountain Night Theme
|
||||||
WV Gold #c9a84c · Navy #1a3a5c · Near-black backgrounds
|
WV gold, electric cyan, aurora magenta, deep black backgrounds
|
||||||
═══════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════ */
|
||||||
:root {
|
:root {
|
||||||
--gold:#c9a84c; --gold-l:#e2c06d; --gold-d:#8a6f2e; --gold-dim:rgba(201,168,76,.13);
|
--gold:#ffd166; --gold-l:#ffe08f; --gold-d:#a57824; --gold-dim:rgba(255,209,102,.14);
|
||||||
--navy:#1a3a5c; --navy-m:#1e4876; --navy-l:#2a5f99;
|
--cyan:#26f4ff; --cyan-l:#8ffbff; --cyan-d:#118895; --cyan-dim:rgba(38,244,255,.13);
|
||||||
--bg0:#07090e; --bg1:#0a0f18; --bg2:#0d1520; --bg3:#111c2c; --bg4:#162436; --bg5:#1c2e46;
|
--pink:#ff4fd8; --pink-l:#ff92ea; --pink-d:#9c2d82; --pink-dim:rgba(255,79,216,.12);
|
||||||
--bdr:#1e3050; --bdr-l:#253d60;
|
--navy:#142142; --navy-m:#1f3769; --navy-l:#3157a5;
|
||||||
--text:#e2ddd0; --text-m:#9aa4b8; --text-d:#58687e;
|
--bg0:#03050b; --bg1:#060914; --bg2:#0a1020; --bg3:#0e172b; --bg4:#121f38; --bg5:#172846;
|
||||||
--green:#2a7a47; --green-l:#3da558; --red:#8f2d2d; --red-l:#c04040; --amber:#b37820;
|
--bdr:#1b3555; --bdr-l:#285076;
|
||||||
--r:6px; --r-lg:12px;
|
--text:#f2f4ff; --text-m:#aab7cf; --text-d:#65768f;
|
||||||
--sh:0 6px 30px rgba(0,0,0,.55); --sh-sm:0 2px 12px rgba(0,0,0,.4);
|
--green:#1d8f62; --green-l:#4dff9b; --red:#9d2c4a; --red-l:#ff5f7b; --amber:#e09a2a;
|
||||||
|
--r:6px; --r-lg:8px;
|
||||||
|
--sh:0 12px 42px rgba(0,0,0,.62),0 0 34px rgba(38,244,255,.06); --sh-sm:0 4px 18px rgba(0,0,0,.45);
|
||||||
--t:.18s ease;
|
--t:.18s ease;
|
||||||
}
|
}
|
||||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||||
html{scroll-behavior:smooth}
|
html{scroll-behavior:smooth}
|
||||||
body{font-family:'Source Sans 3',sans-serif;background:var(--bg1);color:var(--text);line-height:1.65;min-height:100vh;-webkit-font-smoothing:antialiased}
|
body{font-family:'Source Sans 3',sans-serif;background:radial-gradient(circle at 12% 0%,rgba(38,244,255,.12),transparent 28rem),radial-gradient(circle at 88% 8%,rgba(255,79,216,.1),transparent 30rem),linear-gradient(180deg,var(--bg0),var(--bg1) 28rem,var(--bg2));color:var(--text);line-height:1.65;min-height:100vh;-webkit-font-smoothing:antialiased}
|
||||||
a{color:var(--gold);text-decoration:none;transition:color var(--t)}
|
a{color:var(--gold);text-decoration:none;transition:color var(--t)}
|
||||||
a:hover{color:var(--gold-l)}
|
a:hover{color:var(--gold-l)}
|
||||||
h1,h2,h3{font-family:'Playfair Display',serif;line-height:1.15}
|
h1,h2,h3{font-family:'Playfair Display',serif;line-height:1.15}
|
||||||
@@ -25,19 +27,19 @@ button{cursor:pointer;font-family:inherit} input,select,textarea{font-family:inh
|
|||||||
address{font-style:normal}
|
address{font-style:normal}
|
||||||
|
|
||||||
/* ── Navbar ─────────────────────────────────────────────── */
|
/* ── Navbar ─────────────────────────────────────────────── */
|
||||||
.navbar{position:sticky;top:0;z-index:1000;background:rgba(7,9,14,.97);border-bottom:1px solid var(--gold-dim);backdrop-filter:blur(16px)}
|
.navbar{position:sticky;top:0;z-index:1000;background:rgba(3,5,11,.86);border-bottom:1px solid rgba(38,244,255,.18);backdrop-filter:blur(18px);box-shadow:0 1px 0 rgba(255,209,102,.08),0 0 28px rgba(38,244,255,.05)}
|
||||||
.nav-wrap{max-width:1440px;margin:0 auto;padding:0 1.5rem;height:64px;display:flex;align-items:center;justify-content:space-between;gap:1rem}
|
.nav-wrap{max-width:1440px;margin:0 auto;padding:0 1.5rem;height:64px;display:flex;align-items:center;justify-content:space-between;gap:1rem}
|
||||||
.nav-brand{display:flex;align-items:center;gap:.65rem;color:var(--text);flex-shrink:0}
|
.nav-brand{display:flex;align-items:center;gap:.65rem;color:var(--text);flex-shrink:0}
|
||||||
.brand-mtn{font-size:1.9rem;filter:drop-shadow(0 0 10px rgba(201,168,76,.45))}
|
.brand-mtn{font-size:1.9rem;filter:drop-shadow(0 0 10px rgba(38,244,255,.55))}
|
||||||
.nav-brand>div{display:flex;flex-direction:column;line-height:1}
|
.nav-brand>div{display:flex;flex-direction:column;line-height:1}
|
||||||
.brand-city{font-family:'Oswald',sans-serif;font-size:1.15rem;font-weight:600;letter-spacing:.2em;color:var(--gold)}
|
.brand-city{font-family:'Oswald',sans-serif;font-size:1.15rem;font-weight:600;letter-spacing:.2em;color:var(--gold);text-shadow:0 0 16px rgba(255,209,102,.24)}
|
||||||
.brand-wv{font-size:.52rem;letter-spacing:.3em;color:var(--text-m)}
|
.brand-wv{font-size:.52rem;letter-spacing:.3em;color:var(--text-m)}
|
||||||
.nav-links{display:flex;align-items:center;gap:.12rem;flex-wrap:nowrap}
|
.nav-links{display:flex;align-items:center;gap:.12rem;flex-wrap:nowrap}
|
||||||
.nl{display:block;padding:.38rem .72rem;border-radius:4px;font-family:'Oswald',sans-serif;font-size:.78rem;letter-spacing:.1em;color:var(--text-m);transition:all var(--t)}
|
.nl{display:block;padding:.38rem .72rem;border-radius:4px;font-family:'Oswald',sans-serif;font-size:.78rem;letter-spacing:.1em;color:var(--text-m);transition:all var(--t)}
|
||||||
.nl:hover,.nl.active{color:var(--gold);background:var(--gold-dim)}
|
.nl:hover,.nl.active{color:var(--cyan-l);background:var(--cyan-dim)}
|
||||||
.nl-admin{color:var(--gold)!important;border:1px solid var(--gold-dim)}
|
.nl-admin{color:var(--gold)!important;border:1px solid var(--gold-dim)}
|
||||||
.nl-join{background:var(--gold);color:var(--bg0)!important;font-weight:600;padding:.38rem .85rem}
|
.nl-join{background:linear-gradient(135deg,var(--gold),var(--cyan));color:var(--bg0)!important;font-weight:600;padding:.38rem .85rem;box-shadow:0 0 18px rgba(38,244,255,.18)}
|
||||||
.nl-join:hover{background:var(--gold-l);color:var(--bg0)!important}
|
.nl-join:hover{background:linear-gradient(135deg,var(--gold-l),var(--cyan-l));color:var(--bg0)!important}
|
||||||
.user-wrap{position:relative}
|
.user-wrap{position:relative}
|
||||||
.user-btn{background:var(--bg4);border:1px solid var(--bdr-l);color:var(--text-m);padding:.38rem .75rem;border-radius:4px;font-family:'Oswald',sans-serif;font-size:.78rem;letter-spacing:.08em;transition:all var(--t)}
|
.user-btn{background:var(--bg4);border:1px solid var(--bdr-l);color:var(--text-m);padding:.38rem .75rem;border-radius:4px;font-family:'Oswald',sans-serif;font-size:.78rem;letter-spacing:.08em;transition:all var(--t)}
|
||||||
.user-btn:hover{border-color:var(--gold-d);color:var(--gold)}
|
.user-btn:hover{border-color:var(--gold-d);color:var(--gold)}
|
||||||
@@ -60,7 +62,7 @@ address{font-style:normal}
|
|||||||
|
|
||||||
/* ── Hero ──────────────────────────────────────────────── */
|
/* ── Hero ──────────────────────────────────────────────── */
|
||||||
.hero{position:relative;min-height:90vh;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--bg0)}
|
.hero{position:relative;min-height:90vh;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--bg0)}
|
||||||
.hero-bg{position:absolute;inset:0;background:radial-gradient(ellipse 75% 50% at 50% 30%,rgba(26,58,92,.58),transparent 70%),radial-gradient(ellipse 45% 35% at 15% 80%,rgba(201,168,76,.06),transparent),linear-gradient(180deg,var(--bg0),#0c1828 55%,#060a0f)}
|
.hero-bg{position:absolute;inset:0;background:radial-gradient(ellipse 75% 50% at 50% 30%,rgba(38,244,255,.25),transparent 70%),radial-gradient(ellipse 45% 35% at 15% 80%,rgba(255,209,102,.08),transparent),linear-gradient(180deg,var(--bg0),#071526 55%,#03050b)}
|
||||||
.hero-stars{position:absolute;inset:0;pointer-events:none;background:radial-gradient(1px 1px at 8% 10%,rgba(255,255,255,.6),transparent),radial-gradient(2px 2px at 38% 18%,rgba(201,168,76,.55),transparent),radial-gradient(1px 1px at 55% 8%,rgba(255,255,255,.45),transparent),radial-gradient(1px 1px at 72% 22%,rgba(255,255,255,.35),transparent),radial-gradient(1px 1px at 87% 14%,rgba(255,255,255,.5),transparent),radial-gradient(1px 1px at 20% 45%,rgba(255,255,255,.2),transparent),radial-gradient(1px 1px at 62% 42%,rgba(255,255,255,.28),transparent)}
|
.hero-stars{position:absolute;inset:0;pointer-events:none;background:radial-gradient(1px 1px at 8% 10%,rgba(255,255,255,.6),transparent),radial-gradient(2px 2px at 38% 18%,rgba(201,168,76,.55),transparent),radial-gradient(1px 1px at 55% 8%,rgba(255,255,255,.45),transparent),radial-gradient(1px 1px at 72% 22%,rgba(255,255,255,.35),transparent),radial-gradient(1px 1px at 87% 14%,rgba(255,255,255,.5),transparent),radial-gradient(1px 1px at 20% 45%,rgba(255,255,255,.2),transparent),radial-gradient(1px 1px at 62% 42%,rgba(255,255,255,.28),transparent)}
|
||||||
.hero-mtns{position:absolute;bottom:0;left:0;right:0;height:48%;z-index:1}
|
.hero-mtns{position:absolute;bottom:0;left:0;right:0;height:48%;z-index:1}
|
||||||
.hero-mtns svg{width:100%;height:100%}
|
.hero-mtns svg{width:100%;height:100%}
|
||||||
@@ -75,12 +77,12 @@ address{font-style:normal}
|
|||||||
|
|
||||||
/* ── Buttons ────────────────────────────────────────────── */
|
/* ── Buttons ────────────────────────────────────────────── */
|
||||||
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.72rem 1.65rem;border-radius:var(--r);font-family:'Oswald',sans-serif;font-size:.82rem;letter-spacing:.12em;font-weight:500;border:none;cursor:pointer;transition:all var(--t);text-decoration:none;white-space:nowrap}
|
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.72rem 1.65rem;border-radius:var(--r);font-family:'Oswald',sans-serif;font-size:.82rem;letter-spacing:.12em;font-weight:500;border:none;cursor:pointer;transition:all var(--t);text-decoration:none;white-space:nowrap}
|
||||||
.btn-primary{background:var(--gold);color:var(--bg0)}
|
.btn-primary{background:linear-gradient(135deg,var(--gold),var(--cyan));color:var(--bg0);box-shadow:0 0 20px rgba(38,244,255,.16)}
|
||||||
.btn-primary:hover{background:var(--gold-l);color:var(--bg0);transform:translateY(-1px);box-shadow:0 4px 20px rgba(201,168,76,.3)}
|
.btn-primary:hover{background:linear-gradient(135deg,var(--gold-l),var(--cyan-l));color:var(--bg0);transform:translateY(-1px);box-shadow:0 6px 26px rgba(38,244,255,.22)}
|
||||||
.btn-outline{background:transparent;border:1px solid var(--gold-d);color:var(--gold)}
|
.btn-outline{background:rgba(38,244,255,.03);border:1px solid rgba(38,244,255,.42);color:var(--cyan-l)}
|
||||||
.btn-outline:hover{background:var(--gold-dim);border-color:var(--gold)}
|
.btn-outline:hover{background:var(--cyan-dim);border-color:var(--cyan);color:var(--text)}
|
||||||
.btn-secondary{background:var(--bg4);border:1px solid var(--bdr-l);color:var(--text-m)}
|
.btn-secondary{background:var(--bg4);border:1px solid var(--bdr-l);color:var(--text-m)}
|
||||||
.btn-secondary:hover{background:var(--bg5);border-color:var(--gold-d);color:var(--text)}
|
.btn-secondary:hover{background:var(--bg5);border-color:var(--pink-d);color:var(--text)}
|
||||||
.btn-navy{background:var(--navy-m);color:#fff}
|
.btn-navy{background:var(--navy-m);color:#fff}
|
||||||
.btn-navy:hover{background:var(--navy-l)}
|
.btn-navy:hover{background:var(--navy-l)}
|
||||||
.btn-danger{background:var(--red);color:#fff}
|
.btn-danger{background:var(--red);color:#fff}
|
||||||
@@ -92,7 +94,7 @@ address{font-style:normal}
|
|||||||
.btn-block{width:100%;justify-content:center}
|
.btn-block{width:100%;justify-content:center}
|
||||||
|
|
||||||
/* ── Stats ──────────────────────────────────────────────── */
|
/* ── Stats ──────────────────────────────────────────────── */
|
||||||
.stats-strip{background:var(--bg2);border-top:1px solid var(--gold-dim);border-bottom:1px solid var(--gold-dim);padding:2rem 1.5rem}
|
.stats-strip{background:rgba(10,16,32,.78);border-top:1px solid var(--cyan-dim);border-bottom:1px solid var(--gold-dim);padding:2rem 1.5rem}
|
||||||
.stats-inner{max-width:1440px;margin:0 auto;display:flex;justify-content:space-around;flex-wrap:wrap;gap:.5rem}
|
.stats-inner{max-width:1440px;margin:0 auto;display:flex;justify-content:space-around;flex-wrap:wrap;gap:.5rem}
|
||||||
.stat{text-align:center;padding:.75rem 1.25rem}
|
.stat{text-align:center;padding:.75rem 1.25rem}
|
||||||
.stat-n{font-family:'Playfair Display',serif;font-size:2.6rem;font-weight:900;color:var(--gold);line-height:1}
|
.stat-n{font-family:'Playfair Display',serif;font-size:2.6rem;font-weight:900;color:var(--gold);line-height:1}
|
||||||
@@ -106,6 +108,50 @@ address{font-style:normal}
|
|||||||
.eyebrow{font-family:'Oswald',sans-serif;font-size:.68rem;letter-spacing:.32em;color:var(--gold);margin-bottom:.5rem}
|
.eyebrow{font-family:'Oswald',sans-serif;font-size:.68rem;letter-spacing:.32em;color:var(--gold);margin-bottom:.5rem}
|
||||||
.sec-title{font-size:clamp(1.8rem,3vw,2.6rem)}
|
.sec-title{font-size:clamp(1.8rem,3vw,2.6rem)}
|
||||||
.sec-rule{width:44px;height:2px;background:var(--gold);margin-top:.7rem}
|
.sec-rule{width:44px;height:2px;background:var(--gold);margin-top:.7rem}
|
||||||
|
.section-head{display:flex;align-items:end;justify-content:space-between;gap:1rem;margin-bottom:2rem}
|
||||||
|
.card-link{display:block;text-decoration:none;height:100%}
|
||||||
|
.view-label{color:var(--cyan-l);font-size:.74rem;font-family:'Oswald',sans-serif;letter-spacing:.14em}
|
||||||
|
|
||||||
|
/* ── Neon homepage ─────────────────────────────────────── */
|
||||||
|
.home-hero{position:relative;overflow:hidden;min-height:calc(100vh - 64px);display:flex;align-items:center;padding:5.5rem 1.5rem 4.5rem;background:linear-gradient(145deg,rgba(3,5,11,.9),rgba(10,16,32,.98))}
|
||||||
|
.home-hero::before{content:"";position:absolute;inset:0;background:linear-gradient(rgba(38,244,255,.055) 1px,transparent 1px),linear-gradient(90deg,rgba(38,244,255,.045) 1px,transparent 1px);background-size:44px 44px;mask-image:linear-gradient(180deg,rgba(0,0,0,.9),transparent 86%);pointer-events:none}
|
||||||
|
.home-hero-glow{position:absolute;inset:0;background:radial-gradient(circle at 18% 30%,rgba(38,244,255,.22),transparent 24rem),radial-gradient(circle at 78% 20%,rgba(255,79,216,.16),transparent 26rem),radial-gradient(circle at 54% 86%,rgba(255,209,102,.1),transparent 30rem)}
|
||||||
|
.home-hero-grid{position:relative;z-index:1;max-width:1440px;margin:0 auto;width:100%;display:grid;grid-template-columns:minmax(0,1.12fr) minmax(320px,.72fr);gap:3rem;align-items:center}
|
||||||
|
.home-hero-copy{text-align:left;max-width:820px}
|
||||||
|
.home-hero .hero-eyebrow{border-color:rgba(38,244,255,.32);background:rgba(38,244,255,.08);color:var(--cyan-l)}
|
||||||
|
.home-hero .hero-h1{text-align:left;font-size:clamp(3.5rem,9vw,7.4rem);text-shadow:0 0 34px rgba(38,244,255,.2),0 8px 54px rgba(0,0,0,.85)}
|
||||||
|
.home-hero .hero-h1 .gld{background:linear-gradient(90deg,var(--gold),var(--cyan),var(--pink-l));-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
.home-hero .hero-desc{margin:1.2rem 0 0;max-width:650px;color:var(--text-m);font-size:1.12rem}
|
||||||
|
.hero-search{margin-top:2rem;display:flex;gap:.65rem;max-width:670px;position:relative}
|
||||||
|
.hero-search .s-icon{left:1rem;color:var(--cyan)}
|
||||||
|
.hero-search .search-input{padding-left:2.6rem;border-color:rgba(38,244,255,.34);background:rgba(0,0,0,.42)}
|
||||||
|
.home-hero .hero-actions{justify-content:flex-start;margin-top:1.25rem}
|
||||||
|
.hero-signal{display:grid;gap:1rem}
|
||||||
|
.signal-card{background:linear-gradient(180deg,rgba(18,31,56,.82),rgba(6,9,20,.86));border:1px solid rgba(38,244,255,.18);border-radius:var(--r);padding:1.25rem;box-shadow:0 0 28px rgba(38,244,255,.07)}
|
||||||
|
.signal-card-main{min-height:210px;display:flex;flex-direction:column;justify-content:center;border-color:rgba(255,209,102,.26)}
|
||||||
|
.signal-label,.signal-copy{font-family:'Oswald',sans-serif;letter-spacing:.14em;font-size:.68rem;color:var(--text-d);text-transform:uppercase}
|
||||||
|
.signal-number{font-family:'Playfair Display',serif;font-weight:900;font-size:clamp(4rem,8vw,6.5rem);line-height:.95;color:var(--gold);text-shadow:0 0 28px rgba(255,209,102,.2)}
|
||||||
|
.signal-small{font-family:'Playfair Display',serif;font-weight:900;font-size:1.55rem;color:var(--cyan-l);line-height:1.05}
|
||||||
|
.signal-mini-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
||||||
|
.neon-alerts{border-top:1px solid rgba(38,244,255,.14);background:rgba(6,9,20,.78)}
|
||||||
|
.home-band{background:linear-gradient(180deg,rgba(10,16,32,.92),rgba(6,9,20,.92));border-top:1px solid rgba(255,79,216,.13);border-bottom:1px solid rgba(38,244,255,.13)}
|
||||||
|
.event-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem}
|
||||||
|
.event-strip .evt-card{height:100%;margin-bottom:0}
|
||||||
|
.event-empty{padding:2rem;border:1px dashed rgba(38,244,255,.22);border-radius:var(--r);background:rgba(0,0,0,.14)}
|
||||||
|
.about-split{display:grid;grid-template-columns:minmax(0,.95fr) minmax(320px,1fr);gap:3rem;align-items:start}
|
||||||
|
.home-facts{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||||
|
.random-section{padding-top:3rem}
|
||||||
|
.random-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem}
|
||||||
|
.random-row{display:grid;grid-template-columns:44px 1fr auto;align-items:center;gap:.8rem;padding:.9rem 1rem;background:rgba(18,31,56,.78);border:1px solid rgba(38,244,255,.14);border-radius:var(--r);color:var(--text);transition:all var(--t)}
|
||||||
|
.random-row:hover{border-color:rgba(38,244,255,.42);background:rgba(18,31,56,.96);transform:translateY(-1px);box-shadow:var(--sh-sm)}
|
||||||
|
.random-cat{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:rgba(38,244,255,.1);border:1px solid rgba(38,244,255,.2)}
|
||||||
|
.random-main{min-width:0}
|
||||||
|
.random-main strong{display:block;color:var(--text);font-size:.96rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.random-main em{display:block;color:var(--text-d);font-style:normal;font-size:.78rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.random-arrow{color:var(--cyan);font-family:'Oswald',sans-serif}
|
||||||
|
.join-band{max-width:1200px;margin:0 auto 4rem;padding:2rem 1.5rem;display:flex;align-items:center;justify-content:space-between;gap:1.5rem;border-top:1px solid rgba(38,244,255,.18);border-bottom:1px solid rgba(255,209,102,.18)}
|
||||||
|
.join-band h2{font-size:clamp(1.6rem,3vw,2.3rem);margin:.2rem 0 .45rem}
|
||||||
|
.join-band p{max-width:720px;color:var(--text-m);line-height:1.75}
|
||||||
|
|
||||||
/* ── Grids ──────────────────────────────────────────────── */
|
/* ── Grids ──────────────────────────────────────────────── */
|
||||||
.g3{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1.4rem}
|
.g3{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1.4rem}
|
||||||
@@ -113,19 +159,19 @@ address{font-style:normal}
|
|||||||
.g2{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:1.5rem}
|
.g2{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:1.5rem}
|
||||||
|
|
||||||
/* ── Business card ──────────────────────────────────────── */
|
/* ── Business card ──────────────────────────────────────── */
|
||||||
.biz-card{background:var(--bg3);border:1px solid var(--bdr);border-radius:var(--r);overflow:hidden;display:flex;flex-direction:column;transition:all .22s;height:100%}
|
.biz-card{background:linear-gradient(180deg,rgba(18,31,56,.96),rgba(9,15,30,.96));border:1px solid rgba(38,244,255,.16);border-radius:var(--r);overflow:hidden;display:flex;flex-direction:column;transition:all .22s;height:100%;box-shadow:0 0 0 1px rgba(255,209,102,.03)}
|
||||||
.biz-card:hover{transform:translateY(-3px);border-color:var(--gold-d);box-shadow:var(--sh)}
|
.biz-card:hover{transform:translateY(-3px);border-color:rgba(38,244,255,.48);box-shadow:var(--sh)}
|
||||||
.biz-card-top{padding:1.2rem 1.4rem;background:linear-gradient(135deg,var(--bg4),var(--bg3));border-bottom:1px solid var(--bdr)}
|
.biz-card-top{padding:1.2rem 1.4rem;background:linear-gradient(135deg,rgba(38,244,255,.1),rgba(255,79,216,.06),rgba(255,209,102,.05));border-bottom:1px solid rgba(38,244,255,.13)}
|
||||||
.cat-badge{display:inline-block;padding:.16rem .52rem;border-radius:2px;font-family:'Oswald',sans-serif;font-size:.6rem;letter-spacing:.14em;background:var(--gold-dim);border:1px solid rgba(201,168,76,.25);color:var(--gold);margin-bottom:.45rem}
|
.cat-badge{display:inline-block;padding:.16rem .52rem;border-radius:2px;font-family:'Oswald',sans-serif;font-size:.6rem;letter-spacing:.14em;background:var(--cyan-dim);border:1px solid rgba(38,244,255,.28);color:var(--cyan-l);margin-bottom:.45rem}
|
||||||
.biz-name{font-size:1.12rem;font-weight:700;color:var(--text);line-height:1.2}
|
.biz-name{font-size:1.12rem;font-weight:700;color:var(--text);line-height:1.2}
|
||||||
.biz-sub{font-size:.78rem;color:var(--text-d);margin-top:.15rem}
|
.biz-sub{font-size:.78rem;color:var(--text-d);margin-top:.15rem}
|
||||||
.biz-card-body{padding:1rem 1.4rem;flex:1}
|
.biz-card-body{padding:1rem 1.4rem;flex:1}
|
||||||
.biz-desc{font-size:.86rem;color:var(--text-m);line-height:1.65;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
.biz-desc{font-size:.86rem;color:var(--text-m);line-height:1.65;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
||||||
.biz-meta{margin-top:.8rem;display:flex;flex-direction:column;gap:.3rem}
|
.biz-meta{margin-top:.8rem;display:flex;flex-direction:column;gap:.3rem}
|
||||||
.biz-meta-row{display:flex;gap:.45rem;font-size:.78rem;color:var(--text-d);align-items:flex-start}
|
.biz-meta-row{display:flex;gap:.45rem;font-size:.78rem;color:var(--text-d);align-items:flex-start}
|
||||||
.biz-meta-icon{color:var(--gold);flex-shrink:0;width:16px;text-align:center}
|
.biz-meta-icon{color:var(--cyan);flex-shrink:0;width:16px;text-align:center}
|
||||||
.biz-card-foot{padding:.9rem 1.4rem;border-top:1px solid var(--bdr);background:var(--bg4);display:flex;justify-content:space-between;align-items:center}
|
.biz-card-foot{padding:.9rem 1.4rem;border-top:1px solid rgba(38,244,255,.14);background:rgba(18,31,56,.72);display:flex;justify-content:space-between;align-items:center}
|
||||||
.featured-badge{background:linear-gradient(135deg,var(--gold),#a07828);color:var(--bg0);font-family:'Oswald',sans-serif;font-size:.6rem;letter-spacing:.18em;padding:.15rem .5rem;border-radius:2px}
|
.featured-badge{background:linear-gradient(135deg,var(--gold),var(--pink));color:var(--bg0);font-family:'Oswald',sans-serif;font-size:.6rem;letter-spacing:.18em;padding:.15rem .5rem;border-radius:2px}
|
||||||
|
|
||||||
/* ── Stars ──────────────────────────────────────────────── */
|
/* ── Stars ──────────────────────────────────────────────── */
|
||||||
.stars{display:inline-flex;gap:1px}
|
.stars{display:inline-flex;gap:1px}
|
||||||
@@ -209,6 +255,45 @@ address{font-style:normal}
|
|||||||
.fhint{font-size:.75rem;color:var(--text-d);margin-top:.3rem}
|
.fhint{font-size:.75rem;color:var(--text-d);margin-top:.3rem}
|
||||||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
||||||
.err-box{padding:.65rem 1rem;border-radius:4px;margin-bottom:1rem;font-size:.88rem;background:rgba(143,45,45,.28);border:1px solid var(--red-l);color:#ff9a9a}
|
.err-box{padding:.65rem 1rem;border-radius:4px;margin-bottom:1rem;font-size:.88rem;background:rgba(143,45,45,.28);border:1px solid var(--red-l);color:#ff9a9a}
|
||||||
|
.hp-field{position:absolute;left:-9999px;top:-9999px;opacity:0;height:0;width:0;overflow:hidden;pointer-events:none}
|
||||||
|
.tos-check{display:flex;align-items:flex-start;gap:.65rem;background:rgba(18,31,56,.78);border:1px solid rgba(38,244,255,.18);border-radius:var(--r);padding:1rem 1.15rem;cursor:pointer;transition:border-color var(--t);margin:1.4rem 0}
|
||||||
|
.tos-check:has(input:checked){border-color:rgba(77,255,155,.5)}
|
||||||
|
.tos-check input[type="checkbox"]{flex-shrink:0;margin-top:3px;width:17px;height:17px;accent-color:var(--green-l);cursor:pointer}
|
||||||
|
.tos-check-text{font-size:.88rem;color:var(--text-m);line-height:1.6;user-select:none}
|
||||||
|
.tos-check-text strong{color:var(--text)}
|
||||||
|
|
||||||
|
/* ── Signup tiers ──────────────────────────────────────── */
|
||||||
|
.signup-shell{max-width:1040px;margin:0 auto;padding:4rem 1.5rem}
|
||||||
|
.signup-intro{text-align:center;margin-bottom:2rem}
|
||||||
|
.signup-intro .eyebrow{justify-content:center;display:flex;color:var(--cyan-l)}
|
||||||
|
.signup-intro h1{font-size:clamp(2.3rem,5vw,3.5rem);margin:.3rem 0}
|
||||||
|
.signup-intro p{max-width:650px;margin:0 auto;color:var(--text-m);line-height:1.75}
|
||||||
|
.signup-errors{max-width:860px;margin:0 auto 1.25rem}
|
||||||
|
.signup-card{background:linear-gradient(180deg,rgba(14,23,43,.96),rgba(8,13,27,.96));border:1px solid rgba(38,244,255,.18);border-radius:var(--r-lg);padding:1.5rem;box-shadow:var(--sh);position:relative}
|
||||||
|
.tier-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1.5rem}
|
||||||
|
.tier-card{display:grid;gap:.35rem;align-content:start;min-height:170px;border:1px solid rgba(38,244,255,.16);border-radius:var(--r);padding:1.25rem;background:rgba(18,31,56,.7);cursor:pointer;transition:all var(--t)}
|
||||||
|
.tier-card:hover{border-color:rgba(38,244,255,.45);transform:translateY(-1px)}
|
||||||
|
.tier-card input{position:absolute;opacity:0;pointer-events:none}
|
||||||
|
.tier-card:has(input:checked){border-color:var(--gold);background:linear-gradient(180deg,rgba(255,209,102,.12),rgba(38,244,255,.08));box-shadow:0 0 24px rgba(255,209,102,.08)}
|
||||||
|
.tier-card-owner:has(input:checked){border-color:var(--cyan)}
|
||||||
|
.tier-kicker{font-family:'Oswald',sans-serif;font-size:.66rem;letter-spacing:.22em;color:var(--cyan-l);text-transform:uppercase}
|
||||||
|
.tier-title{font-family:'Playfair Display',serif;font-size:1.35rem;color:var(--text);line-height:1.15}
|
||||||
|
.tier-copy{font-size:.9rem;color:var(--text-m);line-height:1.55}
|
||||||
|
.signup-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
||||||
|
.signup-grid .fg{margin-bottom:0}
|
||||||
|
.signup-wide{grid-column:1/-1}
|
||||||
|
.owner-claim-fields{display:none;margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid rgba(38,244,255,.15)}
|
||||||
|
.owner-claim-fields.is-open{display:block}
|
||||||
|
.owner-claim-note{background:rgba(38,244,255,.08);border:1px solid rgba(38,244,255,.22);border-radius:var(--r);padding:1rem 1.15rem;color:var(--text-m);font-size:.9rem;line-height:1.65;margin-bottom:1rem}
|
||||||
|
.owner-claim-note strong{color:var(--cyan-l)}
|
||||||
|
.claim-edit-panel{margin-top:1rem;background:rgba(0,0,0,.16);border:1px solid rgba(255,209,102,.14);border-radius:var(--r);padding:1.2rem}
|
||||||
|
.signup-submit{font-size:.9rem;padding:.85rem}
|
||||||
|
.signin-link{text-align:center;margin-top:1.1rem;font-size:.84rem;color:var(--text-d)}
|
||||||
|
|
||||||
|
/* ── Embedded business video ───────────────────────────── */
|
||||||
|
.business-video-box{border-color:rgba(38,244,255,.24)}
|
||||||
|
.embed-shell{position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;border-radius:var(--r);background:#000;border:1px solid rgba(38,244,255,.22);box-shadow:0 0 26px rgba(38,244,255,.08)}
|
||||||
|
.embed-shell iframe{position:absolute;inset:0;width:100%;height:100%;border:0;background:#000}
|
||||||
|
|
||||||
/* ── Search ─────────────────────────────────────────────── */
|
/* ── Search ─────────────────────────────────────────────── */
|
||||||
.search-bar{background:var(--bg2);padding:1.75rem 1.5rem;border-bottom:1px solid var(--bdr)}
|
.search-bar{background:var(--bg2);padding:1.75rem 1.5rem;border-bottom:1px solid var(--bdr)}
|
||||||
@@ -324,13 +409,14 @@ address{font-style:normal}
|
|||||||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||||||
|
|
||||||
/* ── Responsive ─────────────────────────────────────────── */
|
/* ── Responsive ─────────────────────────────────────────── */
|
||||||
@media(max-width:1100px){.footer-grid{grid-template-columns:1fr 1fr}}
|
@media(max-width:1100px){.footer-grid{grid-template-columns:1fr 1fr}.home-hero-grid{grid-template-columns:1fr}.hero-signal{grid-template-columns:1fr 1fr}.signal-card-main{min-height:auto}.about-split{grid-template-columns:1fr}}
|
||||||
@media(max-width:900px){.detail-body{grid-template-columns:1fr}.admin-wrap{grid-template-columns:1fr}.admin-side{position:static;max-height:none;border-right:none;border-bottom:1px solid var(--bdr)}.form-row{grid-template-columns:1fr}}
|
@media(max-width:900px){.detail-body{grid-template-columns:1fr}.admin-wrap{grid-template-columns:1fr}.admin-side{position:static;max-height:none;border-right:none;border-bottom:1px solid var(--bdr)}.form-row{grid-template-columns:1fr}.section-head{align-items:flex-start;flex-direction:column}.random-list{grid-template-columns:1fr}.signup-grid,.tier-grid{grid-template-columns:1fr}.home-facts{grid-template-columns:1fr}}
|
||||||
@media(max-width:768px){
|
@media(max-width:768px){
|
||||||
.nav-links{display:none;position:absolute;top:64px;left:0;right:0;background:var(--bg0);border-bottom:1px solid var(--bdr-l);flex-direction:column;padding:.75rem 1rem;gap:.1rem;box-shadow:var(--sh)}
|
.nav-links{display:none;position:absolute;top:64px;left:0;right:0;background:var(--bg0);border-bottom:1px solid var(--bdr-l);flex-direction:column;padding:.75rem 1rem;gap:.1rem;box-shadow:var(--sh)}
|
||||||
.nav-links.open{display:flex}.hamburger{display:flex}
|
.nav-links.open{display:flex}.hamburger{display:flex}
|
||||||
.user-wrap{width:100%}.user-btn{width:100%;text-align:left}
|
.user-wrap{width:100%}.user-btn{width:100%;text-align:left}
|
||||||
.user-dd{position:static;box-shadow:none;border:none;background:var(--bg2)}
|
.user-dd{position:static;box-shadow:none;border:none;background:var(--bg2)}
|
||||||
.hero{min-height:80vh}.g3,.g4{grid-template-columns:1fr}.evt-card{flex-direction:column}
|
.hero{min-height:80vh}.g3,.g4{grid-template-columns:1fr}.evt-card{flex-direction:column}
|
||||||
|
.home-hero{min-height:auto;padding:4rem 1rem 3rem}.home-hero .hero-h1{font-size:clamp(3rem,17vw,4.6rem)}.hero-search{flex-direction:column}.hero-search .s-icon{top:1.35rem}.hero-search .btn{justify-content:center}.hero-signal,.signal-mini-grid{grid-template-columns:1fr}.join-band{align-items:flex-start;flex-direction:column;margin-bottom:2.5rem}.signup-shell{padding:3rem 1rem}.signup-card{padding:1rem}.tier-card{min-height:auto}
|
||||||
}
|
}
|
||||||
@media(max-width:560px){.hero-h1{font-size:3rem}.footer-grid{grid-template-columns:1fr}.hrs-grid{grid-template-columns:1fr}.form-box{margin:2rem 1rem}}
|
@media(max-width:560px){.hero-h1{font-size:3rem}.footer-grid{grid-template-columns:1fr}.hrs-grid{grid-template-columns:1fr}.form-box{margin:2rem 1rem}.random-row{grid-template-columns:36px 1fr}.random-arrow{display:none}.home-hero .hero-eyebrow{letter-spacing:.18em}.btn{white-space:normal;text-align:center}.signal-number{font-size:3.6rem}}
|
||||||
|
|||||||
+42
-2
@@ -5,7 +5,7 @@ $slug = gs('slug');
|
|||||||
$b = qone("
|
$b = qone("
|
||||||
SELECT
|
SELECT
|
||||||
b.id, b.name, b.slug, b.subcategory, b.description,
|
b.id, b.name, b.slug, b.subcategory, b.description,
|
||||||
b.address, b.phone, b.email, b.website,
|
b.address, b.phone, b.email, b.website, COALESCE(b.video_url,'') AS video_url,
|
||||||
COALESCE(b.hours,'{}') AS hours,
|
COALESCE(b.hours,'{}') AS hours,
|
||||||
b.lat, b.lng, b.is_active, b.is_featured, b.created_at,
|
b.lat, b.lng, b.is_active, b.is_featured, b.created_at,
|
||||||
b.category_id,
|
b.category_id,
|
||||||
@@ -30,7 +30,7 @@ if (isPost()) {
|
|||||||
// Owner submits edit request
|
// Owner submits edit request
|
||||||
if ($act === 'owner_edit' && owns($b['id']) && !isAdmin()) {
|
if ($act === 'owner_edit' && owns($b['id']) && !isAdmin()) {
|
||||||
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
|
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
|
||||||
$allowedFields = ['description','address','phone','email','is_active'];
|
$allowedFields = ['description','address','phone','email','website','video_url','is_active'];
|
||||||
$submitted = 0;
|
$submitted = 0;
|
||||||
|
|
||||||
// Simple text fields
|
// Simple text fields
|
||||||
@@ -42,6 +42,10 @@ if (isPost()) {
|
|||||||
$new = (string)(int)p('is_active', $b['is_active']);
|
$new = (string)(int)p('is_active', $b['is_active']);
|
||||||
$old = (string)(int)$b['is_active'];
|
$old = (string)(int)$b['is_active'];
|
||||||
}
|
}
|
||||||
|
if ($field === 'video_url' && !validBusinessVideoUrl($new)) {
|
||||||
|
flash('Business videos must be a YouTube or Vimeo URL.', 'error');
|
||||||
|
go("/business.php?slug=$slug");
|
||||||
|
}
|
||||||
if ($new !== $old) {
|
if ($new !== $old) {
|
||||||
// Remove any existing pending request for this field
|
// Remove any existing pending request for this field
|
||||||
qrun("DELETE FROM business_edit_requests WHERE business_id=? AND field=? AND status='pending'",
|
qrun("DELETE FROM business_edit_requests WHERE business_id=? AND field=? AND status='pending'",
|
||||||
@@ -299,6 +303,28 @@ include __DIR__.'/includes/header.php';
|
|||||||
value="<?=e(isset($pendingByField['email']) ? $pendingByField['email']['new_value'] : $b['email'])?>">
|
value="<?=e(isset($pendingByField['email']) ? $pendingByField['email']['new_value'] : $b['email'])?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel">
|
||||||
|
WEBSITE
|
||||||
|
<?php if (isset($pendingByField['website'])): ?><span class="badge badge-gold" style="margin-left:.4rem">PENDING</span><?php endif; ?>
|
||||||
|
</label>
|
||||||
|
<input type="text" name="website" class="finput"
|
||||||
|
value="<?=e(isset($pendingByField['website']) ? $pendingByField['website']['new_value'] : $b['website'])?>"
|
||||||
|
placeholder="example.com">
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel">
|
||||||
|
VIDEO URL
|
||||||
|
<?php if (isset($pendingByField['video_url'])): ?><span class="badge badge-gold" style="margin-left:.4rem">PENDING</span><?php endif; ?>
|
||||||
|
</label>
|
||||||
|
<input type="url" name="video_url" class="finput"
|
||||||
|
value="<?=e(isset($pendingByField['video_url']) ? $pendingByField['video_url']['new_value'] : $b['video_url'])?>"
|
||||||
|
placeholder="YouTube or Vimeo URL">
|
||||||
|
<div class="fhint">Optional. Business owners may use YouTube or Vimeo.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="fg">
|
<div class="fg">
|
||||||
<label class="flabel">
|
<label class="flabel">
|
||||||
HOURS OF OPERATION
|
HOURS OF OPERATION
|
||||||
@@ -352,6 +378,20 @@ include __DIR__.'/includes/header.php';
|
|||||||
<p style="color:var(--text-m);line-height:1.85"><?=e($b['description'] ?? '')?></p>
|
<p style="color:var(--text-m);line-height:1.85"><?=e($b['description'] ?? '')?></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($b['video_url']) && validBusinessVideoUrl($b['video_url'])): ?>
|
||||||
|
<div class="ibox business-video-box" style="margin-bottom:1.75rem">
|
||||||
|
<div class="ibox-title">VIDEO FEATURE</div>
|
||||||
|
<div class="embed-shell">
|
||||||
|
<iframe
|
||||||
|
src="/lone-embed.php?video=<?=rawurlencode($b['video_url'])?>&title=<?=rawurlencode($b['name'])?>"
|
||||||
|
title="<?=e($b['name'])?> video"
|
||||||
|
loading="lazy"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($sects): ?>
|
<?php if ($sects): ?>
|
||||||
<div class="ibox" style="margin-bottom:1.75rem">
|
<div class="ibox" style="margin-bottom:1.75rem">
|
||||||
<div class="ibox-title">🍽️ MENU</div>
|
<div class="ibox-title">🍽️ MENU</div>
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ $myBiz = $bizIds
|
|||||||
? qall("SELECT b.*,c.name cat,COALESCE(AVG(r.rating),0) avg,COUNT(r.id) rcnt FROM businesses b JOIN categories c ON c.id=b.category_id LEFT JOIN reviews r ON r.business_id=b.id WHERE b.id IN(".implode(',',array_fill(0,count($bizIds),'?')).") GROUP BY b.id ORDER BY b.name",$bizIds)
|
? qall("SELECT b.*,c.name cat,COALESCE(AVG(r.rating),0) avg,COUNT(r.id) rcnt FROM businesses b JOIN categories c ON c.id=b.category_id LEFT JOIN reviews r ON r.business_id=b.id WHERE b.id IN(".implode(',',array_fill(0,count($bizIds),'?')).") GROUP BY b.id ORDER BY b.name",$bizIds)
|
||||||
: [];
|
: [];
|
||||||
$myEvents = authed() ? qall("SELECT * FROM events WHERE submitted_by=? ORDER BY created_at DESC LIMIT 10",[uid()]) : [];
|
$myEvents = authed() ? qall("SELECT * FROM events WHERE submitted_by=? ORDER BY created_at DESC LIMIT 10",[uid()]) : [];
|
||||||
|
$myClaims = authed() ? qall("
|
||||||
|
SELECT bcr.*, b.name biz_name, b.slug biz_slug
|
||||||
|
FROM business_claim_requests bcr
|
||||||
|
JOIN businesses b ON b.id=bcr.business_id
|
||||||
|
WHERE bcr.user_id=?
|
||||||
|
ORDER BY bcr.created_at DESC
|
||||||
|
LIMIT 5
|
||||||
|
", [uid()]) : [];
|
||||||
include __DIR__.'/includes/header.php';
|
include __DIR__.'/includes/header.php';
|
||||||
?>
|
?>
|
||||||
<div class="page-hdr">
|
<div class="page-hdr">
|
||||||
@@ -19,6 +27,32 @@ include __DIR__.'/includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
<?php if($myClaims): ?>
|
||||||
|
<div class="eyebrow" style="margin-bottom:1rem">BUSINESS CLAIMS</div>
|
||||||
|
<div class="tbl-wrap" style="margin-bottom:2.5rem">
|
||||||
|
<table class="dtbl">
|
||||||
|
<thead><tr><th>BUSINESS</th><th>CONTACT PHONE</th><th>STATUS</th><th>SUBMITTED</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach($myClaims as $claim): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="td-name"><?=e($claim['biz_name'])?></td>
|
||||||
|
<td><?=e($claim['contact_phone'])?></td>
|
||||||
|
<td>
|
||||||
|
<?php $cc=['approved'=>'badge-green','pending'=>'badge-gold','rejected'=>'badge-red']; ?>
|
||||||
|
<span class="badge <?=$cc[$claim['status']]??'badge-gray'?>"><?=strtoupper($claim['status'])?></span>
|
||||||
|
<?php if($claim['status']==='pending'): ?>
|
||||||
|
<div style="font-size:.76rem;color:var(--text-d);margin-top:.2rem">We will contact you within a few business days for verification.</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?=ago($claim['created_at'] ?? '')?></td>
|
||||||
|
<td><a href="/business.php?slug=<?=e($claim['biz_slug'])?>" class="btn btn-xs btn-outline">View Page</a></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if($myBiz): ?>
|
<?php if($myBiz): ?>
|
||||||
<div class="eyebrow" style="margin-bottom:1rem">MY BUSINESS LISTINGS</div>
|
<div class="eyebrow" style="margin-bottom:1rem">MY BUSINESS LISTINGS</div>
|
||||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:1.4rem;margin-bottom:2.5rem">
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:1.4rem;margin-bottom:2.5rem">
|
||||||
|
|||||||
+46
-5
@@ -18,8 +18,8 @@ function db(): PDO {
|
|||||||
]);
|
]);
|
||||||
$pdo->exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA synchronous=NORMAL;");
|
$pdo->exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA synchronous=NORMAL;");
|
||||||
if ($isNew) { _schema($pdo); _seed($pdo); }
|
if ($isNew) { _schema($pdo); _seed($pdo); }
|
||||||
// Always ensure the edit_requests table exists (for existing DBs)
|
// Always ensure app upgrade tables/columns exist (for existing DBs)
|
||||||
_ensureEditRequests($pdo);
|
_ensureAppUpgrades($pdo);
|
||||||
if (file_exists(DB_FILE)) @chmod(DB_FILE, 0666);
|
if (file_exists(DB_FILE)) @chmod(DB_FILE, 0666);
|
||||||
return $pdo;
|
return $pdo;
|
||||||
}
|
}
|
||||||
@@ -31,8 +31,20 @@ function qone(string $sql, array $p=[]): ?array { $r=qr($sql,$p)->fetch(); retu
|
|||||||
function qval(string $sql, array $p=[]): mixed { return qr($sql,$p)->fetchColumn(); }
|
function qval(string $sql, array $p=[]): mixed { return qr($sql,$p)->fetchColumn(); }
|
||||||
function qrun(string $sql, array $p=[]): int { $s=db()->prepare($sql); $s->execute($p); return (int)db()->lastInsertId(); }
|
function qrun(string $sql, array $p=[]): int { $s=db()->prepare($sql); $s->execute($p); return (int)db()->lastInsertId(); }
|
||||||
|
|
||||||
/* ── Ensure edit requests table exists on old DBs ─── */
|
/* ── Ensure upgraded tables/columns exist on old DBs ─── */
|
||||||
function _ensureEditRequests(PDO $db): void {
|
function _hasColumn(PDO $db, string $table, string $column): bool {
|
||||||
|
$cols = $db->query("PRAGMA table_info($table)")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($cols as $col) {
|
||||||
|
if (($col['name'] ?? '') === $column) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ensureAppUpgrades(PDO $db): void {
|
||||||
|
if (!_hasColumn($db, 'businesses', 'video_url')) {
|
||||||
|
$db->exec("ALTER TABLE businesses ADD COLUMN video_url TEXT NOT NULL DEFAULT ''");
|
||||||
|
}
|
||||||
|
|
||||||
$db->exec("
|
$db->exec("
|
||||||
CREATE TABLE IF NOT EXISTS business_edit_requests (
|
CREATE TABLE IF NOT EXISTS business_edit_requests (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -46,7 +58,24 @@ function _ensureEditRequests(PDO $db): void {
|
|||||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||||
reviewed_at TEXT
|
reviewed_at TEXT
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS business_claim_requests (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
contact_phone TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
admin_notes TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||||
|
reviewed_at TEXT
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_claim_requests_status ON business_claim_requests(status, created_at);
|
||||||
");
|
");
|
||||||
|
|
||||||
|
$defaults = [
|
||||||
|
'home_random_interval' => '2hours',
|
||||||
|
];
|
||||||
|
$stmt = $db->prepare("INSERT OR IGNORE INTO settings(key,value)VALUES(?,?)");
|
||||||
|
foreach ($defaults as $k => $v) $stmt->execute([$k, $v]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════
|
/* ══════════════════════════════════════════════════════
|
||||||
@@ -84,6 +113,7 @@ function _schema(PDO $db): void {
|
|||||||
phone TEXT NOT NULL DEFAULT '',
|
phone TEXT NOT NULL DEFAULT '',
|
||||||
email TEXT NOT NULL DEFAULT '',
|
email TEXT NOT NULL DEFAULT '',
|
||||||
website TEXT NOT NULL DEFAULT '',
|
website TEXT NOT NULL DEFAULT '',
|
||||||
|
video_url TEXT NOT NULL DEFAULT '',
|
||||||
hours TEXT NOT NULL DEFAULT '{}',
|
hours TEXT NOT NULL DEFAULT '{}',
|
||||||
lat REAL NOT NULL DEFAULT 39.4364,
|
lat REAL NOT NULL DEFAULT 39.4364,
|
||||||
lng REAL NOT NULL DEFAULT -78.9762,
|
lng REAL NOT NULL DEFAULT -78.9762,
|
||||||
@@ -108,6 +138,16 @@ function _schema(PDO $db): void {
|
|||||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||||
reviewed_at TEXT
|
reviewed_at TEXT
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS business_claim_requests(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
contact_phone TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
admin_notes TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||||
|
reviewed_at TEXT
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS reviews(
|
CREATE TABLE IF NOT EXISTS reviews(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
||||||
@@ -198,7 +238,8 @@ function _seed(PDO $db): void {
|
|||||||
foreach (['registration_enabled'=>'1','site_name'=>'Discover Keyser WV',
|
foreach (['registration_enabled'=>'1','site_name'=>'Discover Keyser WV',
|
||||||
'site_tagline'=>'The Friendliest City in the U.S.A.',
|
'site_tagline'=>'The Friendliest City in the U.S.A.',
|
||||||
'contact_email'=>'info@discoverkeyser.com',
|
'contact_email'=>'info@discoverkeyser.com',
|
||||||
'events_require_approval'=>'1'] as $k=>$v)
|
'events_require_approval'=>'1',
|
||||||
|
'home_random_interval'=>'2hours'] as $k=>$v)
|
||||||
qrun("INSERT OR IGNORE INTO settings(key,value)VALUES(?,?)",[$k,$v]);
|
qrun("INSERT OR IGNORE INTO settings(key,value)VALUES(?,?)",[$k,$v]);
|
||||||
|
|
||||||
/* Admin user — password "password123" */
|
/* Admin user — password "password123" */
|
||||||
|
|||||||
@@ -135,3 +135,17 @@ function uniqueSlug(string $base): string {
|
|||||||
while (qval("SELECT id FROM businesses WHERE slug=?", [$slug.($i ? "-$i" : "")])) $i++;
|
while (qval("SELECT id FROM businesses WHERE slug=?", [$slug.($i ? "-$i" : "")])) $i++;
|
||||||
return $slug . ($i ? "-$i" : "");
|
return $slug . ($i ? "-$i" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function businessVideoProvider(string $url): string {
|
||||||
|
$host = strtolower((string)(parse_url(trim($url), PHP_URL_HOST) ?? ''));
|
||||||
|
$host = preg_replace('/^www\./', '', $host);
|
||||||
|
if ($host === 'youtu.be' || $host === 'youtube.com' || str_ends_with($host, '.youtube.com') || $host === 'youtube-nocookie.com' || str_ends_with($host, '.youtube-nocookie.com')) return 'YouTube';
|
||||||
|
if ($host === 'vimeo.com' || str_ends_with($host, '.vimeo.com')) return 'Vimeo';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
function validBusinessVideoUrl(string $url): bool {
|
||||||
|
$url = trim($url);
|
||||||
|
if ($url === '') return true;
|
||||||
|
$scheme = strtolower((string)(parse_url($url, PHP_URL_SCHEME) ?? ''));
|
||||||
|
return in_array($scheme, ['http','https'], true) && businessVideoProvider($url) !== '';
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,59 +2,79 @@
|
|||||||
require_once __DIR__.'/includes/boot.php';
|
require_once __DIR__.'/includes/boot.php';
|
||||||
$pageTitle='Discover Keyser WV — The Friendliest City in the U.S.A.';
|
$pageTitle='Discover Keyser WV — The Friendliest City in the U.S.A.';
|
||||||
$activeNav='home';
|
$activeNav='home';
|
||||||
$alerts=qall("SELECT a.*,b.name bname,b.id bid,b.slug bslug FROM alerts a JOIN businesses b ON b.id=a.business_id WHERE a.is_active=1 ORDER BY a.created_at DESC LIMIT 5");
|
|
||||||
$featured=qall("SELECT b.*,c.name cat,c.icon cat_icon,COALESCE(AVG(r.rating),0) avg,COUNT(r.id) rcnt FROM businesses b JOIN categories c ON c.id=b.category_id LEFT JOIN reviews r ON r.business_id=b.id WHERE b.is_active=1 AND b.is_featured=1 GROUP BY b.id ORDER BY avg DESC,rcnt DESC LIMIT 6");
|
$alerts=qall("SELECT a.*,b.name bname,b.id bid,b.slug bslug FROM alerts a JOIN businesses b ON b.id=a.business_id WHERE a.is_active=1 ORDER BY a.created_at DESC LIMIT 4");
|
||||||
|
$featured=qall("SELECT b.*,c.name cat,c.icon cat_icon,COALESCE(AVG(r.rating),0) avg,COUNT(r.id) rcnt FROM businesses b JOIN categories c ON c.id=b.category_id LEFT JOIN reviews r ON r.business_id=b.id AND r.is_approved=1 WHERE b.is_active=1 AND b.is_featured=1 GROUP BY b.id ORDER BY avg DESC,rcnt DESC,b.name LIMIT 6");
|
||||||
$totalBiz=(int)qval("SELECT COUNT(*) FROM businesses WHERE is_active=1");
|
$totalBiz=(int)qval("SELECT COUNT(*) FROM businesses WHERE is_active=1");
|
||||||
$upevts=qall("SELECT * FROM events WHERE status='approved' AND event_date>=date('now') ORDER BY is_featured DESC,event_date ASC LIMIT 3");
|
$upevts=qall("SELECT * FROM events WHERE status='approved' AND event_date>=date('now') ORDER BY is_featured DESC,event_date ASC LIMIT 4");
|
||||||
|
|
||||||
|
$randomInterval = setting('home_random_interval','2hours');
|
||||||
|
$slotKey = $randomInterval === 'day' ? date('Y-m-d') : (string)floor(time() / 7200);
|
||||||
|
$rotationLabel = $randomInterval === 'day' ? 'Refreshes daily' : 'Refreshes every two hours';
|
||||||
|
$randomPool = qall("
|
||||||
|
SELECT b.*,c.name cat,c.icon cat_icon,COALESCE(AVG(r.rating),0) avg,COUNT(r.id) rcnt
|
||||||
|
FROM businesses b
|
||||||
|
JOIN categories c ON c.id=b.category_id
|
||||||
|
LEFT JOIN reviews r ON r.business_id=b.id AND r.is_approved=1
|
||||||
|
WHERE b.is_active=1
|
||||||
|
GROUP BY b.id
|
||||||
|
");
|
||||||
|
usort($randomPool, fn($a,$b) => strcmp(hash('sha256',$slotKey.'-'.$a['id']), hash('sha256',$slotKey.'-'.$b['id'])));
|
||||||
|
$randomBiz = array_slice($randomPool, 0, 8);
|
||||||
|
|
||||||
include __DIR__.'/includes/header.php';
|
include __DIR__.'/includes/header.php';
|
||||||
?>
|
?>
|
||||||
<section class="hero">
|
|
||||||
<div class="hero-bg"></div>
|
<section class="home-hero">
|
||||||
<div class="hero-stars"></div>
|
<div class="home-hero-glow"></div>
|
||||||
<div class="hero-mtns">
|
<div class="home-hero-grid">
|
||||||
<svg viewBox="0 0 1440 300" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
<div class="home-hero-copy">
|
||||||
<polygon points="0,300 0,180 80,120 160,160 240,80 330,138 420,58 520,118 630,38 740,98 850,18 960,88 1060,28 1160,108 1260,48 1380,128 1440,78 1440,300" fill="#0a0f18" opacity=".9"/>
|
<div class="hero-eyebrow">MINERAL COUNTY · WEST VIRGINIA</div>
|
||||||
<polygon points="0,300 0,222 100,170 200,200 310,140 430,188 560,128 680,168 800,100 920,158 1030,108 1140,158 1250,118 1380,175 1440,145 1440,300" fill="#0d1520" opacity=".96"/>
|
<h1 class="hero-h1">KEYSER<span class="gld">AFTER DARK</span></h1>
|
||||||
<polygon points="0,300 0,260 140,232 280,252 420,212 560,242 700,202 840,235 980,197 1120,228 1260,205 1440,225 1440,300" fill="#111c2c"/>
|
<p class="hero-desc">A neon-lit guide to local businesses, upcoming events, mountain history, and the community pages that keep Keyser moving.</p>
|
||||||
<g opacity=".28" stroke="#c9a84c" stroke-width="1.5" fill="none">
|
<form action="/directory.php" method="GET" class="hero-search">
|
||||||
<line x1="850" y1="20" x2="850" y2="72"/><line x1="850" y1="20" x2="833" y2="8"/><line x1="850" y1="20" x2="867" y2="8"/><line x1="850" y1="20" x2="850" y2="5"/>
|
<span class="s-icon">⌕</span>
|
||||||
<line x1="903" y1="30" x2="903" y2="82"/><line x1="903" y1="30" x2="886" y2="18"/><line x1="903" y1="30" x2="920" y2="18"/><line x1="903" y1="30" x2="903" y2="15"/>
|
<input class="search-input" type="text" name="q" placeholder="Search restaurants, shops, services...">
|
||||||
<line x1="958" y1="42" x2="958" y2="94"/><line x1="958" y1="42" x2="941" y2="30"/><line x1="958" y1="42" x2="975" y2="30"/><line x1="958" y1="42" x2="958" y2="27"/>
|
<button class="btn btn-primary">Search</button>
|
||||||
</g>
|
</form>
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hero-content">
|
|
||||||
<div class="hero-eyebrow">⛰ MINERAL COUNTY · EASTERN PANHANDLE · WEST VIRGINIA</div>
|
|
||||||
<h1 class="hero-h1">DISCOVER<span class="gld">KEYSER</span></h1>
|
|
||||||
<div class="hero-sub">NORTH BRANCH POTOMAC RIVER · FOUNDED 1874</div>
|
|
||||||
<div class="hero-rule"></div>
|
|
||||||
<p class="hero-desc">Where Appalachian heritage meets mountain beauty. County seat of Mineral County — 3 hours from Washington D.C., a world away from ordinary.</p>
|
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<a href="/directory.php" class="btn btn-primary">Explore Businesses</a>
|
<a href="/directory.php" class="btn btn-outline">View Directory</a>
|
||||||
<a href="/attractions.php" class="btn btn-outline">Attractions & Activities</a>
|
<a href="/events.php" class="btn btn-secondary">Upcoming Events</a>
|
||||||
|
<?php if(!authed() && setting('registration_enabled','1')==='1'): ?>
|
||||||
|
<a href="/register.php" class="btn btn-primary">Join or Claim a Page</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-signal">
|
||||||
|
<div class="signal-card signal-card-main">
|
||||||
|
<div class="signal-label">Live Directory</div>
|
||||||
|
<div class="signal-number"><?=$totalBiz?></div>
|
||||||
|
<div class="signal-copy">active Keyser business listings</div>
|
||||||
|
</div>
|
||||||
|
<div class="signal-mini-grid">
|
||||||
|
<?php foreach([['1874','Founded'],['809 ft','Elevation'],['WVU','Potomac State'],['North Branch','Potomac River']] as [$n,$l]): ?>
|
||||||
|
<div class="signal-card">
|
||||||
|
<div class="signal-small"><?=$n?></div>
|
||||||
|
<div class="signal-copy"><?=$l?></div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="stats-strip">
|
|
||||||
<div class="stats-inner">
|
|
||||||
<?php foreach([['1874','Founded'],['809 ft','Elevation'],['~4,800','Population'],['132','Wind Turbines'],['1901','PSC Founded'],[$totalBiz,'Businesses Listed']] as [$n,$l]): ?>
|
|
||||||
<div class="stat"><div class="stat-n"><?=e($n)?></div><div class="stat-l"><?=e($l)?></div></div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if($alerts): ?>
|
<?php if($alerts): ?>
|
||||||
<div class="alert-strip">
|
<div class="alert-strip neon-alerts">
|
||||||
<div class="alert-strip-inner">
|
<div class="alert-strip-inner">
|
||||||
<div class="eyebrow" style="margin-bottom:.65rem">📢 LOCAL BUSINESS NEWS & UPDATES</div>
|
<div class="eyebrow">LOCAL BUSINESS SIGNALS</div>
|
||||||
<?php foreach($alerts as $a): ?>
|
<?php foreach($alerts as $a): ?>
|
||||||
<div class="alert-item a-<?=e($a['type'])?>">
|
<div class="alert-item a-<?=e($a['type'])?>">
|
||||||
<div class="alert-ico"><?=alertIcon($a['type'] ?? '')?></div>
|
<div class="alert-ico"><?=alertIcon($a['type'] ?? '')?></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="alert-title"><?=e($a['title'])?></div>
|
<div class="alert-title"><?=e($a['title'])?></div>
|
||||||
<div class="alert-body"><?=e($a['body'])?></div>
|
<div class="alert-body"><?=e($a['body'])?></div>
|
||||||
<div class="alert-meta">From <a href="/business.php?slug=<?=e($a['bslug'])?>"><?=e($a['bname'])?></a> · <?=ago($a['created_at'] ?? '')?></div>
|
<div class="alert-meta">From <a href="/business.php?slug=<?=e($a['bslug'])?>"><?=e($a['bname'])?></a> · <?=ago($a['created_at'] ?? '')?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -63,26 +83,28 @@ include __DIR__.'/includes/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div style="margin-bottom:2rem">
|
<div class="section-head">
|
||||||
<div class="eyebrow">⭐A LITTLE SPOTLIGHT</div>
|
<div>
|
||||||
<h2 class="sec-title">Featured</h2>
|
<div class="eyebrow">FEATURED BUSINESS</div>
|
||||||
<div class="sec-rule"></div>
|
<h2 class="sec-title">Local Places in the Spotlight</h2>
|
||||||
|
</div>
|
||||||
|
<a href="/directory.php" class="btn btn-outline btn-sm">All Businesses</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="g3">
|
<div class="g3">
|
||||||
<?php foreach($featured as $b): ?>
|
<?php foreach($featured as $b): ?>
|
||||||
<a href="/business.php?slug=<?=e($b['slug'])?>" style="text-decoration:none">
|
<a href="/business.php?slug=<?=e($b['slug'])?>" class="card-link">
|
||||||
<div class="biz-card">
|
<div class="biz-card neon-card">
|
||||||
<div class="biz-card-top">
|
<div class="biz-card-top">
|
||||||
<div class="cat-badge"><?=e($b['cat_icon'].' '.$b['cat'])?></div>
|
<div class="cat-badge"><?=e($b['cat_icon'].' '.$b['cat'])?></div>
|
||||||
<?php if($b['is_featured']): ?><span class="featured-badge" style="float:right">★ FEATURED</span><?php endif; ?>
|
<span class="featured-badge">FEATURED</span>
|
||||||
<div class="biz-name"><?=e($b['name'])?></div>
|
<div class="biz-name"><?=e($b['name'])?></div>
|
||||||
<?php if($b['subcategory']): ?><div class="biz-sub"><?=e($b['subcategory'])?></div><?php endif; ?>
|
<?php if($b['subcategory']): ?><div class="biz-sub"><?=e($b['subcategory'])?></div><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="biz-card-body">
|
<div class="biz-card-body">
|
||||||
<div class="biz-desc"><?=e($b['description'])?></div>
|
<div class="biz-desc"><?=e($b['description'])?></div>
|
||||||
<div class="biz-meta">
|
<div class="biz-meta">
|
||||||
<?php if($b['address']): ?><div class="biz-meta-row"><span class="biz-meta-icon">📍</span><?=e($b['address'])?></div><?php endif; ?>
|
<?php if($b['address']): ?><div class="biz-meta-row"><span class="biz-meta-icon">◇</span><?=e($b['address'])?></div><?php endif; ?>
|
||||||
<?php if($b['phone']): ?><div class="biz-meta-row"><span class="biz-meta-icon">📞</span><?=e($b['phone'])?></div><?php endif; ?>
|
<?php if($b['phone']): ?><div class="biz-meta-row"><span class="biz-meta-icon">☎</span><?=e($b['phone'])?></div><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="biz-card-foot">
|
<div class="biz-card-foot">
|
||||||
@@ -91,48 +113,25 @@ include __DIR__.'/includes/header.php';
|
|||||||
<span class="rat-score"><?=number_format((float)$b['avg'],1)?></span>
|
<span class="rat-score"><?=number_format((float)$b['avg'],1)?></span>
|
||||||
<span class="rat-cnt">(<?=$b['rcnt']?>)</span>
|
<span class="rat-cnt">(<?=$b['rcnt']?>)</span>
|
||||||
</div>
|
</div>
|
||||||
<span style="color:var(--gold);font-size:.76rem;font-family:'Oswald',sans-serif">VIEW →</span>
|
<span class="view-label">VIEW</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:center;margin-top:2rem">
|
|
||||||
<a href="/directory.php" class="btn btn-outline">Browse Full Directory (<?=$totalBiz?> Businesses)</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="background:var(--bg2);border-top:1px solid var(--bdr);border-bottom:1px solid var(--bdr);padding:4rem 1.5rem">
|
<div class="home-band">
|
||||||
<div style="max-width:1100px;margin:0 auto;display:grid;grid-template-columns:1fr 1fr;gap:3rem;align-items:center">
|
<div class="section">
|
||||||
|
<div class="section-head">
|
||||||
<div>
|
<div>
|
||||||
<div class="eyebrow">OUR HISTORY</div>
|
<div class="eyebrow">UPCOMING EVENTS</div>
|
||||||
<h2 class="sec-title" style="margin-bottom:1rem">A City with Deep Mountain Roots</h2>
|
<h2 class="sec-title">What’s Happening Around Keyser</h2>
|
||||||
<div class="prose">
|
|
||||||
<p>Originally called <strong>Paddy Town</strong> after Irish settler Patrick McCarty, then New Creek, Keyser took its name in 1874 — honoring William Keyser, Vice President of the Baltimore & Ohio Railroad whose line transformed the region in 1842.</p>
|
|
||||||
<p>Today Keyser is home to <strong>Potomac State College of WVU</strong> (est. 1901 on historic Civil War Fort Fuller), WVU Medicine Potomac Valley Hospital, and a vibrant downtown community rich with Appalachian culture on the banks of the North Branch Potomac River.</p>
|
|
||||||
<p>Mineral County is part of the <strong>Appalachian Forest National Heritage Area</strong> — over 500 square miles of forested beauty where American history lives on in every ridge and hollow.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<a href="/about.php" class="btn btn-outline" style="margin-top:1rem">Learn More About Keyser</a>
|
<a href="/events.php" class="btn btn-outline btn-sm">All Events</a>
|
||||||
</div>
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
|
||||||
<?php foreach([['🏛️','HISTORIC','National Register of Historic Places sites'],['🎓','EDUCATION','Potomac State College of WVU since 1901'],['🌊','OUTDOORS','Kayaking & fishing the North Branch'],['💨','WIND ENERGY','132 turbines on the Allegheny Front']] as [$ic,$lbl,$val]): ?>
|
|
||||||
<div style="background:var(--bg3);border:1px solid var(--bdr);border-radius:6px;padding:1.2rem;text-align:center">
|
|
||||||
<div style="font-size:2rem;margin-bottom:.45rem"><?=$ic?></div>
|
|
||||||
<div style="font-family:'Oswald',sans-serif;font-size:.66rem;letter-spacing:.2em;color:var(--gold)"><?=$lbl?></div>
|
|
||||||
<div style="color:var(--text);margin-top:.25rem;font-size:.88rem"><?=$val?></div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if($upevts): ?>
|
|
||||||
<div class="section">
|
|
||||||
<div style="margin-bottom:2rem">
|
|
||||||
<div class="eyebrow">📅 WHAT'S HAPPENING</div>
|
|
||||||
<h2 class="sec-title">Upcoming Events</h2>
|
|
||||||
<div class="sec-rule"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<?php if($upevts): ?>
|
||||||
|
<div class="event-strip">
|
||||||
<?php foreach($upevts as $ev): ?>
|
<?php foreach($upevts as $ev): ?>
|
||||||
<div class="evt-card">
|
<div class="evt-card">
|
||||||
<div class="evt-date-box">
|
<div class="evt-date-box">
|
||||||
@@ -141,34 +140,86 @@ include __DIR__.'/includes/header.php';
|
|||||||
<div class="evt-yr"><?=date('Y',strtotime($ev['event_date']))?></div>
|
<div class="evt-yr"><?=date('Y',strtotime($ev['event_date']))?></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="evt-body">
|
<div class="evt-body">
|
||||||
<div class="evt-title"><?=e($ev['title'])?><?php if($ev['is_featured']): ?> <span class="featured-badge" style="margin-left:.5rem">★ FEATURED</span><?php endif; ?></div>
|
<div class="evt-title"><?=e($ev['title'])?><?php if($ev['is_featured']): ?> <span class="featured-badge">FEATURED</span><?php endif; ?></div>
|
||||||
<div class="evt-meta">
|
<div class="evt-meta">
|
||||||
<?php if($ev['venue']): ?><span>📍 <?=e($ev['venue'])?></span><?php endif; ?>
|
<?php if($ev['venue']): ?><span><?=e($ev['venue'])?></span><?php endif; ?>
|
||||||
<?php if($ev['event_time']): ?><span>🕐 <?=e($ev['event_time'])?></span><?php endif; ?>
|
<?php if($ev['event_time']): ?><span><?=e($ev['event_time'])?></span><?php endif; ?>
|
||||||
<span>💰 <?=e($ev['cost'])?></span>
|
<span><?=e($ev['cost'])?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="evt-desc"><?=e(substr($ev['description'],0,180)).(strlen($ev['description'])>180?'…':'')?></div>
|
<div class="evt-desc"><?=e(substr($ev['description'],0,160)).(strlen($ev['description'])>160?'...':'')?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<div style="text-align:center;margin-top:1.5rem">
|
|
||||||
<a href="/events.php" class="btn btn-outline">All Events</a>
|
|
||||||
<a href="/events.php?submit=1" class="btn btn-secondary" style="margin-left:.75rem">Submit an Event</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div style="padding:4rem 1.5rem;text-align:center;max-width:560px;margin:0 auto">
|
|
||||||
<div class="eyebrow">JOIN THE COMMUNITY</div>
|
|
||||||
<h2 style="font-size:2rem;margin:.5rem 0 1rem">Own a Business in Keyser?</h2>
|
|
||||||
<p style="color:var(--text-m);margin-bottom:2rem;line-height:1.85">Create an account to manage your listing, post alerts, add menus, and connect with the Keyser community.</p>
|
|
||||||
<div class="flex-row" style="justify-content:center">
|
|
||||||
<?php if(!authed()): ?>
|
|
||||||
<a href="/register.php" class="btn btn-primary">Create Account</a>
|
|
||||||
<a href="/login.php" class="btn btn-outline">Login</a>
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a href="/dashboard.php" class="btn btn-primary">My Dashboard</a>
|
<div class="empty event-empty">
|
||||||
|
<div class="empty-ico">◇</div>
|
||||||
|
<h3>No upcoming approved events yet</h3>
|
||||||
|
<p style="margin-top:.5rem;color:var(--text-d)">Community events will appear here as they are approved.</p>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<div class="text-center mt3">
|
||||||
|
<a href="/events.php?submit=1" class="btn btn-primary">Submit an Event</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="section about-split">
|
||||||
|
<div>
|
||||||
|
<div class="eyebrow">ABOUT KEYSER</div>
|
||||||
|
<h2 class="sec-title">Railroad Roots, River Views, Mountain Pace</h2>
|
||||||
|
<div class="prose">
|
||||||
|
<p>Keyser began as Paddy Town, then New Creek, before taking its current name in 1874 in honor of Baltimore & Ohio Railroad leader William Keyser. The rail line shaped the city’s downtown, its work ethic, and its role as Mineral County’s county seat.</p>
|
||||||
|
<p>Today the city sits where New Creek meets the North Branch Potomac River, with Potomac State College of WVU, local restaurants, small shops, healthcare, civic life, and outdoor access all close by.</p>
|
||||||
|
</div>
|
||||||
|
<a href="/about.php" class="btn btn-outline">Learn More About Keyser</a>
|
||||||
|
</div>
|
||||||
|
<div class="fact-grid home-facts">
|
||||||
|
<?php foreach([['Historic Core','Downtown, rail heritage, and civic landmarks'],['Outdoor Access','River routes, nearby trails, and mountain drives'],['College Town','Home to Potomac State College of WVU'],['Local Directory',$totalBiz.' active business pages and growing']] as [$lbl,$val]): ?>
|
||||||
|
<div class="fact-card">
|
||||||
|
<div class="fact-lbl"><?=e($lbl)?></div>
|
||||||
|
<div class="fact-val"><?=e($val)?></div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section random-section">
|
||||||
|
<div class="section-head">
|
||||||
|
<div>
|
||||||
|
<div class="eyebrow">RANDOM LOCAL PICKS · <?=e(strtoupper($rotationLabel))?></div>
|
||||||
|
<h2 class="sec-title">Explore Something Different</h2>
|
||||||
|
</div>
|
||||||
|
<a href="/directory.php" class="btn btn-primary btn-sm">View All Businesses</a>
|
||||||
|
</div>
|
||||||
|
<div class="random-list">
|
||||||
|
<?php foreach($randomBiz as $b): ?>
|
||||||
|
<a href="/business.php?slug=<?=e($b['slug'])?>" class="random-row">
|
||||||
|
<span class="random-cat"><?=e($b['cat_icon'])?></span>
|
||||||
|
<span class="random-main">
|
||||||
|
<strong><?=e($b['name'])?></strong>
|
||||||
|
<em><?=e($b['cat'])?><?= $b['subcategory'] ? ' · '.e($b['subcategory']) : '' ?></em>
|
||||||
|
</span>
|
||||||
|
<span class="random-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt3">
|
||||||
|
<a href="/directory.php" class="btn btn-outline">Browse the Full Business Directory</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="join-band">
|
||||||
|
<div>
|
||||||
|
<div class="eyebrow">BUSINESS OWNERS</div>
|
||||||
|
<h2>Claim your Keyser business page</h2>
|
||||||
|
<p>Create a business-owner account, choose the listing you manage, add a verification phone number, and submit first edits or a YouTube/Vimeo video for admin review.</p>
|
||||||
|
</div>
|
||||||
|
<?php if(!authed() && setting('registration_enabled','1')==='1'): ?>
|
||||||
|
<a href="/register.php" class="btn btn-primary">Start a Claim</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="/dashboard.php" class="btn btn-primary">Open Dashboard</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php include __DIR__.'/includes/footer.php'; ?>
|
<?php include __DIR__.'/includes/footer.php'; ?>
|
||||||
|
|||||||
+1322
File diff suppressed because it is too large
Load Diff
+186
-210
@@ -8,286 +8,262 @@ if (setting('registration_enabled','1') !== '1') {
|
|||||||
|
|
||||||
$pageTitle = 'Create Account — Discover Keyser WV';
|
$pageTitle = 'Create Account — Discover Keyser WV';
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
$businessOptions = qall("SELECT id,name,address FROM businesses WHERE is_active=1 ORDER BY name");
|
||||||
|
$tier = p('member_type', 'member') === 'business_owner' ? 'business_owner' : 'member';
|
||||||
|
|
||||||
if (isPost()) {
|
if (isPost()) {
|
||||||
csrfCheck();
|
csrfCheck();
|
||||||
|
|
||||||
// ── Honeypot check — bots fill hidden fields, humans don't ──
|
if (ps('website_url') !== '' || ps('confirm_email') !== '') {
|
||||||
// Field named "website_url" is visually hidden; any value = bot
|
|
||||||
if (ps('website_url') !== '') {
|
|
||||||
// Silently succeed (don't tell the bot it failed)
|
|
||||||
flash('Account created! Welcome to Discover Keyser WV.','success');
|
flash('Account created! Welcome to Discover Keyser WV.','success');
|
||||||
go('/index.php');
|
go('/index.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Timing check — real humans take at least 3 seconds ──
|
|
||||||
$formTime = (int)p('_form_time', 0);
|
$formTime = (int)p('_form_time', 0);
|
||||||
if ($formTime && (time() - $formTime) < 3) {
|
if ($formTime && (time() - $formTime) < 3) {
|
||||||
// Also silent — pretend success
|
|
||||||
flash('Account created! Welcome to Discover Keyser WV.','success');
|
flash('Account created! Welcome to Discover Keyser WV.','success');
|
||||||
go('/index.php');
|
go('/index.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tier = p('member_type', 'member') === 'business_owner' ? 'business_owner' : 'member';
|
||||||
$un = ps('username');
|
$un = ps('username');
|
||||||
$em = ps('email');
|
$em = ps('email');
|
||||||
$pw = ps('password');
|
$pw = ps('password');
|
||||||
$pw2 = ps('password2');
|
$pw2 = ps('password2');
|
||||||
$tos = p('lawful_use', '');
|
$tos = p('lawful_use', '');
|
||||||
|
|
||||||
if (strlen($un) < 3)
|
$claimBizId = (int)p('claim_business_id');
|
||||||
$errors[] = 'Username must be at least 3 characters.';
|
$contactPhone = ps('contact_phone');
|
||||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $un))
|
$claimBiz = null;
|
||||||
$errors[] = 'Username may only contain letters, numbers, and underscores.';
|
|
||||||
if ($em && !filter_var($em, FILTER_VALIDATE_EMAIL))
|
if (strlen($un) < 3) $errors[] = 'Username must be at least 3 characters.';
|
||||||
$errors[] = 'Please enter a valid email address.';
|
if (!preg_match('/^[a-zA-Z0-9_]+$/', $un)) $errors[] = 'Username may only contain letters, numbers, and underscores.';
|
||||||
if (strlen($pw) < 6)
|
if ($em && !filter_var($em, FILTER_VALIDATE_EMAIL)) $errors[] = 'Please enter a valid email address.';
|
||||||
$errors[] = 'Password must be at least 6 characters.';
|
if (strlen($pw) < 6) $errors[] = 'Password must be at least 6 characters.';
|
||||||
if ($pw !== $pw2)
|
if ($pw !== $pw2) $errors[] = 'Passwords do not match.';
|
||||||
$errors[] = 'Passwords do not match.';
|
if ($tos !== '1') $errors[] = 'You must agree to use this service lawfully to create an account.';
|
||||||
if ($tos !== '1')
|
if (!$errors && qval("SELECT id FROM users WHERE username=?", [$un])) $errors[] = 'That username is already taken. Please choose another.';
|
||||||
$errors[] = 'You must agree to use this service lawfully to create an account.';
|
if (!$errors && $em && qval("SELECT id FROM users WHERE email=?", [$em])) $errors[] = 'That email address is already registered. Try signing in instead.';
|
||||||
if (!$errors && qval("SELECT id FROM users WHERE username=?", [$un]))
|
|
||||||
$errors[] = 'That username is already taken. Please choose another.';
|
$claimEdits = [
|
||||||
if (!$errors && $em && qval("SELECT id FROM users WHERE email=?", [$em]))
|
'description' => ps('claim_description'),
|
||||||
$errors[] = 'That email address is already registered. Try signing in instead.';
|
'address' => ps('claim_address'),
|
||||||
|
'phone' => ps('claim_public_phone'),
|
||||||
|
'email' => ps('claim_public_email'),
|
||||||
|
'website' => ps('claim_website'),
|
||||||
|
'video_url' => ps('claim_video_url'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($tier === 'business_owner') {
|
||||||
|
if (!$claimBizId) {
|
||||||
|
$errors[] = 'Please select the business page you want to claim.';
|
||||||
|
} else {
|
||||||
|
$claimBiz = qone("SELECT * FROM businesses WHERE id=? AND is_active=1", [$claimBizId]);
|
||||||
|
if (!$claimBiz) $errors[] = 'Please select a valid listed business.';
|
||||||
|
}
|
||||||
|
if ($contactPhone === '' || strlen(preg_replace('/\D+/', '', $contactPhone)) < 7) {
|
||||||
|
$errors[] = 'Please enter a contact phone number for verification.';
|
||||||
|
}
|
||||||
|
if ($claimEdits['email'] !== '' && !filter_var($claimEdits['email'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$errors[] = 'Please enter a valid public email edit.';
|
||||||
|
}
|
||||||
|
if (!validBusinessVideoUrl($claimEdits['video_url'])) {
|
||||||
|
$errors[] = 'Business videos must be a YouTube or Vimeo URL.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$id = qrun(
|
$id = qrun(
|
||||||
"INSERT INTO users(username,email,password)VALUES(?,?,?)",
|
"INSERT INTO users(username,email,password)VALUES(?,?,?)",
|
||||||
[$un, $em ?: null, password_hash($pw, PASSWORD_BCRYPT)]
|
[$un, $em ?: null, password_hash($pw, PASSWORD_BCRYPT)]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$submittedEdits = 0;
|
||||||
|
if ($tier === 'business_owner' && $claimBiz) {
|
||||||
|
qrun(
|
||||||
|
"INSERT INTO business_claim_requests(business_id,user_id,contact_phone)VALUES(?,?,?)",
|
||||||
|
[$claimBizId, $id, $contactPhone]
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($claimEdits as $field => $newValue) {
|
||||||
|
if ($newValue === '') continue;
|
||||||
|
$oldValue = (string)($claimBiz[$field] ?? '');
|
||||||
|
if ($newValue === $oldValue) continue;
|
||||||
|
qrun(
|
||||||
|
"INSERT INTO business_edit_requests(business_id,user_id,field,old_value,new_value)VALUES(?,?,?,?,?)",
|
||||||
|
[$claimBizId, $id, $field, $oldValue, $newValue]
|
||||||
|
);
|
||||||
|
$submittedEdits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loginUser(qone("SELECT * FROM users WHERE id=?", [$id]));
|
loginUser(qone("SELECT * FROM users WHERE id=?", [$id]));
|
||||||
flash('Welcome to Discover Keyser WV, '.e($un).'!', 'success');
|
|
||||||
|
if ($tier === 'business_owner' && $claimBiz) {
|
||||||
|
$msg = 'Your account is ready. Your claim for '.$claimBiz['name'].' is queued, and we will contact you at '.$contactPhone.' within a few business days for verification.';
|
||||||
|
if ($submittedEdits) $msg .= ' '.$submittedEdits.' proposed edit'.($submittedEdits !== 1 ? 's were' : ' was').' sent to admin review.';
|
||||||
|
flash($msg, 'success');
|
||||||
|
go('/dashboard.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
flash('Welcome to Discover Keyser WV, '.$un.'!', 'success');
|
||||||
go('/index.php');
|
go('/index.php');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ownerSelected = $tier === 'business_owner';
|
||||||
|
|
||||||
|
$footExtra = <<<HTML
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const radios = document.querySelectorAll('input[name="member_type"]');
|
||||||
|
const ownerFields = document.getElementById('ownerClaimFields');
|
||||||
|
const sync = () => {
|
||||||
|
const owner = document.querySelector('input[name="member_type"]:checked')?.value === 'business_owner';
|
||||||
|
ownerFields?.classList.toggle('is-open', owner);
|
||||||
|
ownerFields?.querySelectorAll('[data-owner-required]').forEach(el => {
|
||||||
|
if (owner) el.setAttribute('required', 'required');
|
||||||
|
else el.removeAttribute('required');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
radios.forEach(r => r.addEventListener('change', sync));
|
||||||
|
sync();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
HTML;
|
||||||
|
|
||||||
include __DIR__.'/includes/header.php';
|
include __DIR__.'/includes/header.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<style>
|
<div class="signup-shell">
|
||||||
/* Honeypot field — visually gone, still in DOM for bots */
|
<div class="signup-intro">
|
||||||
.hp-field {
|
<div class="eyebrow">JOIN KEYSER ONLINE</div>
|
||||||
position:absolute;
|
<h1>Create Your Account</h1>
|
||||||
left:-9999px;
|
<p>Choose a community membership or start a verified business-owner claim for a page already listed in the directory.</p>
|
||||||
top:-9999px;
|
|
||||||
opacity:0;
|
|
||||||
height:0;
|
|
||||||
width:0;
|
|
||||||
overflow:hidden;
|
|
||||||
pointer-events:none;
|
|
||||||
tabindex:-1;
|
|
||||||
}
|
|
||||||
.benefit-grid {
|
|
||||||
display:grid;
|
|
||||||
grid-template-columns:1fr 1fr;
|
|
||||||
gap:1rem;
|
|
||||||
margin-bottom:2rem;
|
|
||||||
}
|
|
||||||
@media(max-width:640px){.benefit-grid{grid-template-columns:1fr}}
|
|
||||||
.benefit-card {
|
|
||||||
background:var(--bg3);
|
|
||||||
border:1px solid var(--bdr);
|
|
||||||
border-radius:var(--r);
|
|
||||||
padding:1.4rem;
|
|
||||||
}
|
|
||||||
.benefit-card h3 {
|
|
||||||
font-family:'Oswald',sans-serif;
|
|
||||||
font-size:.88rem;
|
|
||||||
letter-spacing:.14em;
|
|
||||||
color:var(--gold);
|
|
||||||
margin-bottom:.85rem;
|
|
||||||
padding-bottom:.55rem;
|
|
||||||
border-bottom:1px solid var(--bdr);
|
|
||||||
}
|
|
||||||
.benefit-card ul {
|
|
||||||
list-style:none;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
.benefit-card ul li {
|
|
||||||
display:flex;
|
|
||||||
gap:.55rem;
|
|
||||||
align-items:flex-start;
|
|
||||||
font-size:.86rem;
|
|
||||||
color:var(--text-m);
|
|
||||||
line-height:1.55;
|
|
||||||
margin-bottom:.55rem;
|
|
||||||
}
|
|
||||||
.benefit-card ul li:last-child{margin-bottom:0}
|
|
||||||
.benefit-card ul li .bi {flex-shrink:0;margin-top:1px}
|
|
||||||
.owner-note {
|
|
||||||
background:rgba(26,58,92,.25);
|
|
||||||
border:1px solid rgba(42,95,153,.4);
|
|
||||||
border-radius:var(--r);
|
|
||||||
padding:1.1rem 1.3rem;
|
|
||||||
font-size:.86rem;
|
|
||||||
color:var(--text-m);
|
|
||||||
line-height:1.7;
|
|
||||||
margin-bottom:2rem;
|
|
||||||
}
|
|
||||||
.owner-note strong {color:var(--gold-l)}
|
|
||||||
.tos-check {
|
|
||||||
display:flex;
|
|
||||||
align-items:flex-start;
|
|
||||||
gap:.65rem;
|
|
||||||
background:var(--bg4);
|
|
||||||
border:1px solid var(--bdr-l);
|
|
||||||
border-radius:var(--r);
|
|
||||||
padding:1rem 1.15rem;
|
|
||||||
cursor:pointer;
|
|
||||||
transition:border-color var(--t);
|
|
||||||
}
|
|
||||||
.tos-check:has(input:checked) {border-color:var(--green-l)}
|
|
||||||
.tos-check input[type="checkbox"] {
|
|
||||||
flex-shrink:0;
|
|
||||||
margin-top:3px;
|
|
||||||
width:17px;
|
|
||||||
height:17px;
|
|
||||||
accent-color:var(--green-l);
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
.tos-check-text {
|
|
||||||
font-size:.88rem;
|
|
||||||
color:var(--text-m);
|
|
||||||
line-height:1.6;
|
|
||||||
user-select:none;
|
|
||||||
}
|
|
||||||
.tos-check-text strong {color:var(--text)}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div style="max-width:860px;margin:3rem auto;padding:0 1.5rem">
|
|
||||||
|
|
||||||
<!-- Page header -->
|
|
||||||
<div style="text-align:center;margin-bottom:2rem">
|
|
||||||
<div class="eyebrow" style="justify-content:center;display:flex">JOIN THE COMMUNITY</div>
|
|
||||||
<h1 style="font-size:clamp(2rem,4vw,2.8rem);margin:.4rem 0">Create Your Account</h1>
|
|
||||||
<p style="color:var(--text-m);font-size:1rem;max-width:520px;margin:.6rem auto 0;line-height:1.7">
|
|
||||||
Discover Keyser WV is your community hub for local businesses, events, and information.
|
|
||||||
Takes less than a minute.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Benefits -->
|
|
||||||
<div class="benefit-grid">
|
|
||||||
<div class="benefit-card">
|
|
||||||
<h3>👤 COMMUNITY MEMBER BENEFITS</h3>
|
|
||||||
<ul>
|
|
||||||
<li><span class="bi">⭐</span>Rate and review local Keyser businesses</li>
|
|
||||||
<li><span class="bi">📅</span>Submit community events to the public calendar</li>
|
|
||||||
<li><span class="bi">🚩</span>Report incorrect or outdated business listings</li>
|
|
||||||
<li><span class="bi">💬</span>Share your experiences with the Keyser community</li>
|
|
||||||
<li><span class="bi">📍</span>Access your personal dashboard with your submissions</li>
|
|
||||||
<li><span class="bi">🔔</span>Stay connected with what's happening in Mineral County</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="benefit-card" style="border-color:rgba(26,72,118,.5)">
|
|
||||||
<h3>🏢 BUSINESS OWNER BENEFITS</h3>
|
|
||||||
<ul>
|
|
||||||
<li><span class="bi">✏️</span>Update your listing — description, hours, phone, address</li>
|
|
||||||
<li><span class="bi">📢</span>Post alerts and announcements directly on your business page</li>
|
|
||||||
<li><span class="bi">🍽️</span>Build and manage a full menu for your restaurant or café</li>
|
|
||||||
<li><span class="bi">🟢</span>Mark your business open or temporarily closed in real time</li>
|
|
||||||
<li><span class="bi">📊</span>View your listing's reviews and ratings from one dashboard</li>
|
|
||||||
<li><span class="bi">🔑</span>All changes are reviewed by our admin team before going live</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Business owner request note -->
|
|
||||||
<div class="owner-note">
|
|
||||||
<strong>🏢 Do you own or manage a Keyser business?</strong> Create your account below,
|
|
||||||
then <strong>contact our admin team</strong> at
|
|
||||||
<a href="mailto:<?=e(setting('contact_email','info@discoverkeyser.com'))?>" style="color:var(--gold-l)"><?=e(setting('contact_email','info@discoverkeyser.com'))?></a>
|
|
||||||
with your username and the name of your business.
|
|
||||||
An administrator will link your account to your listing so you can start managing it.
|
|
||||||
This verification step helps us ensure only legitimate owners control business pages.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Registration form -->
|
|
||||||
<div style="background:var(--bg3);border:1px solid var(--bdr);border-radius:var(--r-lg);padding:2.25rem">
|
|
||||||
<h2 style="font-family:'Oswald',sans-serif;font-size:1rem;letter-spacing:.14em;color:var(--text);margin-bottom:1.5rem;padding-bottom:.65rem;border-bottom:1px solid var(--bdr)">
|
|
||||||
CREATE YOUR ACCOUNT
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<?php if ($errors): ?>
|
<?php if ($errors): ?>
|
||||||
<div class="err-box" style="margin-bottom:1.25rem"><?=implode('<br>', array_map('e', $errors))?></div>
|
<div class="err-box signup-errors"><?=implode('<br>', array_map('e', $errors))?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="POST" autocomplete="off">
|
<form method="POST" autocomplete="off" class="signup-card">
|
||||||
<?=csrfField()?>
|
<?=csrfField()?>
|
||||||
<!-- Timing token -->
|
|
||||||
<input type="hidden" name="_form_time" value="<?=time()?>">
|
<input type="hidden" name="_form_time" value="<?=time()?>">
|
||||||
|
|
||||||
<!-- ── HONEYPOT fields — hidden from humans, filled by bots ── -->
|
|
||||||
<div class="hp-field" aria-hidden="true">
|
<div class="hp-field" aria-hidden="true">
|
||||||
<label for="website_url">Leave this blank</label>
|
<label for="website_url">Leave this blank</label>
|
||||||
<input type="text" id="website_url" name="website_url" value=""
|
<input type="text" id="website_url" name="website_url" value="" tabindex="-1" autocomplete="off">
|
||||||
tabindex="-1" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<div class="hp-field" aria-hidden="true">
|
|
||||||
<label for="confirm_email">Do not fill</label>
|
<label for="confirm_email">Do not fill</label>
|
||||||
<input type="email" id="confirm_email" name="confirm_email" value=""
|
<input type="email" id="confirm_email" name="confirm_email" value="" tabindex="-1" autocomplete="off">
|
||||||
tabindex="-1" autocomplete="off">
|
|
||||||
</div>
|
</div>
|
||||||
<!-- ── END HONEYPOT ─────────────────────────────────────────── -->
|
|
||||||
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
<div class="tier-grid">
|
||||||
<div class="fg" style="margin-bottom:0">
|
<label class="tier-card">
|
||||||
|
<input type="radio" name="member_type" value="member"<?=!$ownerSelected?' checked':''?>>
|
||||||
|
<span class="tier-kicker">Regular Member</span>
|
||||||
|
<span class="tier-title">Explore and contribute</span>
|
||||||
|
<span class="tier-copy">Rate businesses, submit events, report outdated listings, and keep up with Keyser.</span>
|
||||||
|
</label>
|
||||||
|
<label class="tier-card tier-card-owner">
|
||||||
|
<input type="radio" name="member_type" value="business_owner"<?=$ownerSelected?' checked':''?>>
|
||||||
|
<span class="tier-kicker">Business Owner</span>
|
||||||
|
<span class="tier-title">Claim a listed page</span>
|
||||||
|
<span class="tier-copy">Request ownership, send initial corrections, and add an optional YouTube or Vimeo video.</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="signup-grid">
|
||||||
|
<div class="fg">
|
||||||
<label class="flabel" for="username">USERNAME *</label>
|
<label class="flabel" for="username">USERNAME *</label>
|
||||||
<input type="text" id="username" name="username" class="finput"
|
<input type="text" id="username" name="username" class="finput"
|
||||||
value="<?=e($_POST['username']??'')?>"
|
value="<?=e($_POST['username']??'')?>"
|
||||||
autocomplete="username" required autofocus
|
autocomplete="username" required autofocus pattern="[a-zA-Z0-9_]+" minlength="3">
|
||||||
pattern="[a-zA-Z0-9_]+" minlength="3">
|
|
||||||
<div class="fhint">Letters, numbers, underscores only. Min 3 characters.</div>
|
<div class="fhint">Letters, numbers, underscores only. Min 3 characters.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fg" style="margin-bottom:0">
|
<div class="fg">
|
||||||
<label class="flabel" for="email">EMAIL ADDRESS <span style="font-weight:300;letter-spacing:0">(optional)</span></label>
|
<label class="flabel" for="email">EMAIL ADDRESS <span style="font-weight:300;letter-spacing:0">(optional)</span></label>
|
||||||
<input type="email" id="email" name="email" class="finput"
|
<input type="email" id="email" name="email" class="finput"
|
||||||
value="<?=e($_POST['email']??'')?>"
|
value="<?=e($_POST['email']??'')?>"
|
||||||
autocomplete="email">
|
autocomplete="email">
|
||||||
<div class="fhint">Used only for account recovery. Never shared.</div>
|
<div class="fhint">Used for account recovery and owner verification follow-up.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="fg">
|
||||||
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem">
|
|
||||||
<div class="fg" style="margin-bottom:0">
|
|
||||||
<label class="flabel" for="password">PASSWORD *</label>
|
<label class="flabel" for="password">PASSWORD *</label>
|
||||||
<input type="password" id="password" name="password" class="finput"
|
<input type="password" id="password" name="password" class="finput" autocomplete="new-password" required minlength="6">
|
||||||
autocomplete="new-password" required minlength="6">
|
|
||||||
<div class="fhint">Minimum 6 characters.</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="fg" style="margin-bottom:0">
|
<div class="fg">
|
||||||
<label class="flabel" for="password2">CONFIRM PASSWORD *</label>
|
<label class="flabel" for="password2">CONFIRM PASSWORD *</label>
|
||||||
<input type="password" id="password2" name="password2" class="finput"
|
<input type="password" id="password2" name="password2" class="finput" autocomplete="new-password" required minlength="6">
|
||||||
autocomplete="new-password" required minlength="6">
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="ownerClaimFields" class="owner-claim-fields<?=$ownerSelected?' is-open':''?>">
|
||||||
|
<div class="owner-claim-note">
|
||||||
|
<strong>Verification required.</strong> Select the business page you manage and provide a contact phone number. A site administrator will contact you within a few business days before the page is assigned to your account.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="signup-grid">
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="claim_business_id">BUSINESS TO CLAIM *</label>
|
||||||
|
<select id="claim_business_id" name="claim_business_id" class="fselect" data-owner-required>
|
||||||
|
<option value="">Select a listed business</option>
|
||||||
|
<?php foreach ($businessOptions as $biz): ?>
|
||||||
|
<option value="<?=$biz['id']?>"<?=((int)($_POST['claim_business_id']??0)===(int)$biz['id'])?' selected':''?>>
|
||||||
|
<?=e($biz['name'])?><?= $biz['address'] ? ' — '.e($biz['address']) : '' ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="contact_phone">CONTACT PHONE FOR VERIFICATION *</label>
|
||||||
|
<input type="tel" id="contact_phone" name="contact_phone" class="finput"
|
||||||
|
value="<?=e($_POST['contact_phone']??'')?>" placeholder="304-555-0123" data-owner-required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="claim-edit-panel">
|
||||||
|
<div class="ibox-title">OPTIONAL FIRST EDITS</div>
|
||||||
|
<div class="signup-grid">
|
||||||
|
<div class="fg signup-wide">
|
||||||
|
<label class="flabel" for="claim_description">DESCRIPTION UPDATE</label>
|
||||||
|
<textarea id="claim_description" name="claim_description" class="ftextarea" placeholder="Share a corrected or refreshed business description."><?=e($_POST['claim_description']??'')?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="claim_address">PUBLIC ADDRESS</label>
|
||||||
|
<input type="text" id="claim_address" name="claim_address" class="finput" value="<?=e($_POST['claim_address']??'')?>">
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="claim_public_phone">PUBLIC PHONE</label>
|
||||||
|
<input type="text" id="claim_public_phone" name="claim_public_phone" class="finput" value="<?=e($_POST['claim_public_phone']??'')?>">
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="claim_public_email">PUBLIC EMAIL</label>
|
||||||
|
<input type="email" id="claim_public_email" name="claim_public_email" class="finput" value="<?=e($_POST['claim_public_email']??'')?>">
|
||||||
|
</div>
|
||||||
|
<div class="fg">
|
||||||
|
<label class="flabel" for="claim_website">WEBSITE</label>
|
||||||
|
<input type="text" id="claim_website" name="claim_website" class="finput" value="<?=e($_POST['claim_website']??'')?>" placeholder="example.com">
|
||||||
|
</div>
|
||||||
|
<div class="fg signup-wide">
|
||||||
|
<label class="flabel" for="claim_video_url">VIDEO URL</label>
|
||||||
|
<input type="url" id="claim_video_url" name="claim_video_url" class="finput" value="<?=e($_POST['claim_video_url']??'')?>" placeholder="YouTube or Vimeo URL">
|
||||||
|
<div class="fhint">Optional. Business owners may use YouTube or Vimeo.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Terms / lawful use checkbox -->
|
|
||||||
<div style="margin:1.5rem 0 1.25rem">
|
|
||||||
<label class="tos-check">
|
<label class="tos-check">
|
||||||
<input type="checkbox" name="lawful_use" value="1"
|
<input type="checkbox" name="lawful_use" value="1" <?=(!empty($_POST) && p('lawful_use')==='1') ? 'checked' : ''?> required>
|
||||||
<?=(!empty($_POST) && p('lawful_use')==='1') ? 'checked' : ''?> required>
|
|
||||||
<span class="tos-check-text">
|
<span class="tos-check-text">
|
||||||
<strong>I will only use this service lawfully.</strong>
|
<strong>I will only use this service lawfully.</strong>
|
||||||
I agree not to post false, misleading, defamatory, or harmful content.
|
I agree not to post false, misleading, defamatory, or harmful content, and I understand fraudulent business claims may be removed.
|
||||||
I understand that accounts used for spam, abuse, or fraudulent business claims
|
|
||||||
will be removed without notice.
|
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-block" style="font-size:.9rem;padding:.85rem">
|
<button type="submit" class="btn btn-primary btn-block signup-submit">Create Account</button>
|
||||||
Create Account
|
<p class="signin-link">Already have an account? <a href="/login.php">Sign in here</a></p>
|
||||||
</button>
|
|
||||||
|
|
||||||
<p style="text-align:center;margin-top:1.1rem;font-size:.84rem;color:var(--text-d)">
|
|
||||||
Already have an account? <a href="/login.php">Sign in here</a>
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include __DIR__.'/includes/footer.php'; ?>
|
<?php include __DIR__.'/includes/footer.php'; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user