feat: add footer with copyright, Gitea repo link, and live dev ticker
- © Jason Stedwell copyright with current year - Gitea icon + link to https://git.alwisp.com/jason/cpas - Running elapsed time ticker since first commit (2026-03-06T11:33:32) ticks every second: Xd HHh MMm SSs format - App layout changed to flex column so footer pins to page bottom - Footer styles isolated in `sf` object for clarity
This commit is contained in:
1 parent
87649b59d0
commit
7e1164af13
1 file changed
+1
-76
+1
-76
@@ -1,76 +1 @@
|
|||||||
import React, { useState } from 'react';
|
aW1wb3J0IFJlYWN0LCB7IHVzZVN0YXRlLCB1c2VFZmZlY3QgfSBmcm9tICdyZWFjdCc7CmltcG9ydCBWaW9sYXRpb25Gb3JtIGZyb20gJy4vY29tcG9uZW50cy9WaW9sYXRpb25Gb3JtJzsKaW1wb3J0IERhc2hib2FyZCAgICAgZnJvbSAnLi9jb21wb25lbnRzL0Rhc2hib2FyZCc7CmltcG9ydCBSZWFkbWVNb2RhbCAgIGZyb20gJy4vY29tcG9uZW50cy9SZWFkbWVNb2RhbCc7CmltcG9ydCBUb2FzdFByb3ZpZGVyIGZyb20gJy4vY29tcG9uZW50cy9Ub2FzdFByb3ZpZGVyJzsKCmNvbnN0IFJFUE9fVVJMICAgICAgPSAnaHR0cHM6Ly9naXQuYWx3aXNwLmNvbS9qYXNvbi9jcGFzJzsKLy8gRmlyc3QgY29tbWl0IHRpbWVzdGFtcCDigJQgdXNlZCB0byBkcml2ZSB0aGUgZGV2IHRpY2tlcgpjb25zdCBQUk9KRUNUX1NUQVJUID0gbmV3IERhdGUoJzIwMjYtMDMtMDZUMTE6MzM6MzItMDY6MDAnKTsKCmNvbnN0IHRhYnMgPSBbCiAgeyBpZDogJ2Rhc2hib2FyZCcsIGxhYmVsOiAn8J+TiiBEYXNoYm9hcmQnIH0sCiAgeyBpZDogJ3Zpb2xhdGlvbicsIGxhYmVsOiAnKyBOZXcgVmlvbGF0aW9uJyB9LApdOwoKLyog4pSA4pSAIGhlbHBlcnMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAICovCmZ1bmN0aW9uIGVsYXBzZWQoZnJvbSkgewogIGNvbnN0IGRpZmYgPSBNYXRoLmZsb29yKChEYXRlLm5vdygpIC0gZnJvbS5nZXRUaW1lKCkpIC8gMTAwMCk7CiAgY29uc3QgZCA9IE1hdGguZmxvb3IoZGlmZiAvIDg2NDAwKTsKICBjb25zdCBoID0gTWF0aC5mbG9vcigoZGlmZiAlIDg2NDAwKSAvIDM2MDApOwogIGNvbnN0IG0gPSBNYXRoLmZsb29yKChkaWZmICUgMzYwMCkgLyA2MCk7CiAgY29uc3QgcyA9IGRpZmYgJSA2MDsKICByZXR1cm4gYCR7ZH1kICR7U3RyaW5nKGgpLnBhZFN0YXJ0KDIsJzAnKX1oICR7U3RyaW5nKG0pLnBhZFN0YXJ0KDIsJzAnKX1tICR7U3RyaW5nKHMpLnBhZFN0YXJ0KDIsJzAnKX1zYDsKfQoKLyog4pSA4pSAIHN1Yi1jb21wb25lbnRzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgCAqLwpmdW5jdGlvbiBEZXZUaWNrZXIoKSB7CiAgY29uc3QgW3RpY2ssIHNldFRpY2tdID0gdXNlU3RhdGUoKCkgPT4gZWxhcHNlZChQUk9KRUNUX1NUQVJUKSk7CiAgdXNlRWZmZWN0KCgpID0+IHsKICAgIGNvbnN0IGlkID0gc2V0SW50ZXJ2YWwoKCkgPT4gc2V0VGljayhlbGFwc2VkKFBST0pFQ1RfU1RBUlQpKSwgMTAwMCk7CiAgICByZXR1cm4gKCkgPT4gY2xlYXJJbnRlcnZhbChpZCk7CiAgfSwgW10pOwogIHJldHVybiAoCiAgICA8c3BhbiBzdHlsZT17c2YudGlja2VyfSB0aXRsZT0iVGltZSBzaW5jZSBmaXJzdCBjb21taXQiPgogICAgICA8c3BhbiBzdHlsZT17c2YudGlja2VyRG90fSAvPgogICAgICB7dGlja30KICAgIDwvc3Bhbj4KICApOwp9CgpmdW5jdGlvbiBHaXRlYUljb24oKSB7CiAgLy8gU2ltcGxlIEdpdGVhLXN0eWxlIGljb24gdXNpbmcgU1ZHIHBhdGgKICByZXR1cm4gKAogICAgPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iY3VycmVudENvbG9yIiBzdHlsZT17eyBkaXNwbGF5OidibG9jaycgfX0+CiAgICAgIDxwYXRoIGQ9Ik0xMiAyQzYuNDc3IDIgMiA2LjQ3NyAyIDEyYzAgNC40MTggMi44NjUgOC4xNjYgNi44MzkgOS40ODkuNS4wOTIuNjgyLS4yMTcuNjgyLS40ODIKICAgICAgICAgICAgICAgMC0uMjM3LS4wMDktLjg2OC0uMDEzLTEuNzAzLTIuNzgyLjYwNS0zLjM2OS0xLjM0LTMuMzY5LTEuMzQtLjQ1NC0xLjE1NC0xLjExLTEuNDYyCiAgICAgICAgICAgICAgIC0xLjExLTEuNDYyLS45MDgtLjYyLjA2OS0uNjA4LjA2OS0uNjA4IDEuMDAzLjA3IDEuNTMxIDEuMDMgMS41MzEgMS4wMy44OTIgMS41MjkKICAgICAgICAgICAgICAgMi4zNDEgMS4wODcgMi45MS44MzIuMDkyLS42NDcuMzUtMS4wODguNjM2LTEuMzM4LTIuMjItLjI1My00LjU1NS0xLjExLTQuNTU1LTQuOTQzCiAgICAgICAgICAgICAgIDAtMS4wOTEuMzktMS45ODQgMS4wMjktMi42ODMtLjEwMy0uMjUzLS40NDYtMS4yNy4wOTgtMi42NDcgMCAwIC44NC0uMjY5IDIuNzUgMS4wMjUKICAgICAgICAgICAgICAgQTkuNTY0IDkuNTY0IDAgMDExMiA2Ljg0NGE5LjU5IDkuNTkgMCAwMTIuNTA0LjMzN2MxLjkwOS0xLjI5NCAyLjc0Ny0xLjAyNSAyLjc0Ny0xLjAyNQogICAgICAgICAgICAgICAuNTQ2IDEuMzc3LjIwMiAyLjM5NC4xIDIuNjQ3LjY0LjY5OSAxLjAyOCAxLjU5MiAxLjAyOCAyLjY4MyAwIDMuODQyLTIuMzM5IDQuNjg3CiAgICAgICAgICAgICAgIC00LjU2NiA0LjkzNS4zNTkuMzA5LjY3OC45MTkuNjc4IDEuODUyIDAgMS4zMzYtLjAxMiAyLjQxNS0uMDEyIDIuNzQzIDAgLjI2Ny4xOC41NzgKICAgICAgICAgICAgICAgLjY4OC40OEMxOS4xMzggMjAuMTYzIDIyIDE2LjQxOCAyMiAxMmMwLTUuNTIzLTQuNDc3LTEwLTEwLTEweiIvPgogICAgPC9zdmc+CiAgKTsKfQoKZnVuY3Rpb24gQXBwRm9vdGVyKCkgewogIHJldHVybiAoCiAgICA8Zm9vdGVyIHN0eWxlPXtzZi5mb290ZXJ9PgogICAgICA8c3BhbiBzdHlsZT17c2YuY29weX0+wqkge25ldyBEYXRlKCkuZ2V0RnVsbFllYXIoKX0gSmFzb24gU3RlZHdlbGw8L3NwYW4+CiAgICAgIDxzcGFuIHN0eWxlPXtzZi5zZXB9PsK3PC9zcGFuPgogICAgICA8RGV2VGlja2VyIC8+CiAgICAgIDxzcGFuIHN0eWxlPXtzZi5zZXB9PsK3PC9zcGFuPgogICAgICA8YSBocmVmPXtSRVBPX1VSTH0gdGFyZ2V0PSJfYmxhbmsiIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciIgc3R5bGU9e3NmLnJlcG9MaW5rfSB0aXRsZT0iVmlldyBzb3VyY2Ugb24gR2l0ZWEiPgogICAgICAgIDxHaXRlYUljb24gLz4KICAgICAgICA8c3Bhbj5jcGFzPC9zcGFuPgogICAgICA8L2E+CiAgICA8L2Zvb3Rlcj4KICApOwp9CgovKiDilIDilIAgc3R5bGVzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgCAqLwpjb25zdCBzID0gewogIGFwcDogICAgICB7IG1pbkhlaWdodDogJzEwMHZoJywgYmFja2dyb3VuZDogJyMwNTA2MDgnLCBmb250RmFtaWx5OiAiJ1NlZ29lIFVJJywgQXJpYWwsIHNhbnMtc2VyaWYiLCBjb2xvcjogJyNmOGY5ZmEnLCBkaXNwbGF5OiAnZmxleCcsIGZsZXhEaXJlY3Rpb246ICdjb2x1bW4nIH0sCiAgbmF2OiAgICAgIHsgYmFja2dyb3VuZDogJyMwMDAwMDAnLCBwYWRkaW5nOiAnMCA0MHB4JywgZGlzcGxheTogJ2ZsZXgnLCBhbGlnbkl0ZW1zOiAnY2VudGVyJywgZ2FwOiAwLCBib3JkZXJCb3R0b206ICcxcHggc29saWQgIzMzMycgfSwKICBsb2dvV3JhcDogeyBkaXNwbGF5OiAnZmxleCcsIGFsaWduSXRlbXM6ICdjZW50ZXInLCBtYXJnaW5SaWdodDogJzMycHgnLCBwYWRkaW5nOiAnMTRweCAwJyB9LAogIGxvZ29JbWc6ICB7IGhlaWdodDogJzI4cHgnLCBtYXJnaW5Line truncated
|
||||||
import ViolationForm from './components/ViolationForm';
|
|
||||||
import Dashboard from './components/Dashboard';
|
|
||||||
import ReadmeModal from './components/ReadmeModal';
|
|
||||||
import ToastProvider from './components/ToastProvider';
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ id: 'dashboard', label: '📊 Dashboard' },
|
|
||||||
{ id: 'violation', label: '+ New Violation' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const s = {
|
|
||||||
app: { minHeight: '100vh', background: '#050608', fontFamily: "'Segoe UI', Arial, sans-serif", color: '#f8f9fa' },
|
|
||||||
nav: { background: '#000000', padding: '0 40px', display: 'flex', alignItems: 'center', gap: 0, borderBottom: '1px solid #333' },
|
|
||||||
logoWrap: { display: 'flex', alignItems: 'center', marginRight: '32px', padding: '14px 0' },
|
|
||||||
logoImg: { height: '28px', marginRight: '10px' },
|
|
||||||
logoText: { color: '#f8f9fa', fontWeight: 800, fontSize: '18px', letterSpacing: '0.5px' },
|
|
||||||
tab: (active) => ({
|
|
||||||
padding: '18px 22px',
|
|
||||||
color: active ? '#f8f9fa' : 'rgba(248,249,250,0.6)',
|
|
||||||
borderBottom: active ? '3px solid #d4af37' : '3px solid transparent',
|
|
||||||
cursor: 'pointer', fontWeight: active ? 700 : 400, fontSize: '14px',
|
|
||||||
background: 'none', border: 'none',
|
|
||||||
}),
|
|
||||||
// Docs button sits flush-right in the nav
|
|
||||||
docsBtn: {
|
|
||||||
marginLeft: 'auto',
|
|
||||||
background: 'none',
|
|
||||||
border: '1px solid #2a2b3a',
|
|
||||||
color: '#9ca0b8',
|
|
||||||
borderRadius: '6px',
|
|
||||||
padding: '6px 14px',
|
|
||||||
fontSize: '12px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontWeight: 600,
|
|
||||||
letterSpacing: '0.3px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
},
|
|
||||||
card: { maxWidth: '1100px', margin: '30px auto', background: '#111217', borderRadius: '10px', boxShadow: '0 2px 16px rgba(0,0,0,0.6)', border: '1px solid #222' },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const [tab, setTab] = useState('dashboard');
|
|
||||||
const [showReadme, setShowReadme] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToastProvider>
|
|
||||||
<div style={s.app}>
|
|
||||||
<nav style={s.nav}>
|
|
||||||
<div style={s.logoWrap}>
|
|
||||||
<img src="/static/mpm-logo.png" alt="MPM" style={s.logoImg} />
|
|
||||||
<div style={s.logoText}>CPAS Tracker</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{tabs.map(t => (
|
|
||||||
<button key={t.id} style={s.tab(tab === t.id)} onClick={() => setTab(t.id)}>
|
|
||||||
{t.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<button style={s.docsBtn} onClick={() => setShowReadme(true)} title="Open admin documentation">
|
|
||||||
<span>?</span> Docs
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div style={s.card}>
|
|
||||||
{tab === 'dashboard' ? <Dashboard /> : <ViolationForm />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showReadme && <ReadmeModal onClose={() => setShowReadme(false)} />}
|
|
||||||
</div>
|
|
||||||
</ToastProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user