feat(admin): Add version API endpoint and refactor admin/moderation layouts

- Create new `/api/version` endpoint to retrieve git commit count and hash information
- Simplify admin page layout by removing unnecessary nested divs and card wrapper
- Refactor moderation page tab navigation with improved button styling using btn-sm and btn-primary classes
- Consolidate padding and styling in moderation page for cleaner component structure
- Add word-break and overflow-wrap styles to report content display for better text handling
- Improve responsive design by adding minWidth: 0 to flex containers in report cards
- Streamline conditional rendering in admin settings to reduce DOM nesting depth
This commit is contained in:
Christomatt
2026-01-26 18:58:34 +01:00
parent 02e7987512
commit a3b158099d
5 changed files with 85 additions and 56 deletions
+4 -8
View File
@@ -233,12 +233,10 @@ export default function AdminPage() {
<h1 style={{ fontSize: '18px', fontWeight: 600 }}>Admin Settings</h1>
</header>
<div style={{ padding: '16px' }}>
<div className="card" style={{ padding: '24px' }}>
{loading ? (
<div style={{ padding: '48px', textAlign: 'center', color: 'var(--foreground-tertiary)' }}>Loading settings...</div>
) : (
<div style={{ display: 'grid', gap: '16px', maxWidth: '600px' }}>
{loading ? (
<div style={{ padding: '48px', textAlign: 'center', color: 'var(--foreground-tertiary)' }}>Loading settings...</div>
) : (
<div style={{ display: 'grid', gap: '16px', maxWidth: '600px', padding: '16px' }}>
<div>
<label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Node Name</label>
<input
@@ -532,8 +530,6 @@ export default function AdminPage() {
</div>
</div>
)}
</div>
</div>
</>
);
}
+34
View File
@@ -0,0 +1,34 @@
import { NextResponse } from 'next/server';
import { execSync } from 'child_process';
export async function GET() {
try {
// Get the total number of commits
const commitCount = execSync('git rev-list --count HEAD', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore'] // Ignore stderr
}).trim();
// Also get the short hash for reference
const commitHash = execSync('git rev-parse --short HEAD', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore']
}).trim();
return NextResponse.json({
count: parseInt(commitCount, 10),
hash: commitHash,
fullHash: execSync('git rev-parse HEAD', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore']
}).trim()
});
} catch (error) {
// If git is not available or not a git repo, return unknown
return NextResponse.json({
count: null,
hash: 'unknown',
fullHash: null
});
}
}
+30 -45
View File
@@ -189,45 +189,29 @@ export default function ModerationPage() {
<h1 style={{ fontSize: '18px', fontWeight: 600 }}>Moderation</h1>
</header>
<div style={{ padding: '16px' }}>
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px', borderBottom: '1px solid var(--border)' }}>
<button
className={`btn btn-ghost ${tab === 'reports' ? '' : ''}`}
onClick={() => setTab('reports')}
style={{
borderRadius: 0,
borderBottom: tab === 'reports' ? '2px solid var(--accent)' : '2px solid transparent',
fontWeight: tab === 'reports' ? 600 : 400,
}}
>
Reports
</button>
<button
className={`btn btn-ghost ${tab === 'posts' ? '' : ''}`}
onClick={() => setTab('posts')}
style={{
borderRadius: 0,
borderBottom: tab === 'posts' ? '2px solid var(--accent)' : '2px solid transparent',
fontWeight: tab === 'posts' ? 600 : 400,
}}
>
Posts
</button>
<button
className={`btn btn-ghost ${tab === 'users' ? '' : ''}`}
onClick={() => setTab('users')}
style={{
borderRadius: 0,
borderBottom: tab === 'users' ? '2px solid var(--accent)' : '2px solid transparent',
fontWeight: tab === 'users' ? 600 : 400,
}}
>
Users
</button>
</div>
<div style={{ display: 'flex', gap: '8px', padding: '16px', borderBottom: '1px solid var(--border)' }}>
<button
className={`btn btn-sm ${tab === 'reports' ? 'btn-primary' : 'btn-ghost'}`}
onClick={() => setTab('reports')}
>
Reports
</button>
<button
className={`btn btn-sm ${tab === 'posts' ? 'btn-primary' : 'btn-ghost'}`}
onClick={() => setTab('posts')}
>
Posts
</button>
<button
className={`btn btn-sm ${tab === 'users' ? 'btn-primary' : 'btn-ghost'}`}
onClick={() => setTab('users')}
>
Users
</button>
</div>
{tab === 'reports' && (
<div className="card" style={{ padding: '16px' }}>
{tab === 'reports' && (
<div style={{ padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', flexWrap: 'wrap', gap: '12px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
{(['open', 'resolved', 'all'] as const).map((status) => (
@@ -256,7 +240,7 @@ export default function ModerationPage() {
{reports.map((report) => (
<div key={report.id} className="card" style={{ padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '16px' }}>
<div style={{ flex: 1 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
<span style={{
fontSize: '11px',
@@ -283,6 +267,8 @@ export default function ModerationPage() {
background: 'var(--background-secondary)',
borderRadius: '8px',
fontSize: '14px',
wordBreak: 'break-word',
overflowWrap: 'break-word',
}}>
<strong>@{report.target.author.handle}:</strong> {report.target.content || '[repost]'}
</div>
@@ -329,7 +315,7 @@ export default function ModerationPage() {
)}
{tab === 'posts' && (
<div className="card" style={{ padding: '16px' }}>
<div style={{ padding: '16px' }}>
{loading ? (
<div style={{ padding: '48px', textAlign: 'center', color: 'var(--foreground-tertiary)' }}>Loading posts...</div>
) : posts.length === 0 ? (
@@ -339,7 +325,7 @@ export default function ModerationPage() {
{posts.map((post) => (
<div key={post.id} className="card" style={{ padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '16px' }}>
<div style={{ flex: 1 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
<span style={{
fontSize: '11px',
@@ -356,7 +342,7 @@ export default function ModerationPage() {
@{post.author.handle} {formatDate(post.createdAt)}
</span>
</div>
<div style={{ marginBottom: '8px' }}>{post.content || '[repost]'}</div>
<div style={{ marginBottom: '8px', wordBreak: 'break-word', overflowWrap: 'break-word' }}>{post.content || '[repost]'}</div>
{post.removedReason && (
<div style={{ fontSize: '13px', color: 'var(--foreground-secondary)' }}>
Reason: {post.removedReason}
@@ -383,7 +369,7 @@ export default function ModerationPage() {
)}
{tab === 'users' && (
<div className="card" style={{ padding: '16px' }}>
<div style={{ padding: '16px' }}>
{loading ? (
<div style={{ padding: '48px', textAlign: 'center', color: 'var(--foreground-tertiary)' }}>Loading users...</div>
) : users.length === 0 ? (
@@ -393,7 +379,7 @@ export default function ModerationPage() {
{users.map((user) => (
<div key={user.id} className="card" style={{ padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '16px', alignItems: 'flex-start' }}>
<div style={{ flex: 1 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px', flexWrap: 'wrap' }}>
<span style={{
fontSize: '11px',
@@ -483,7 +469,6 @@ export default function ModerationPage() {
)}
</div>
)}
</div>
</>
);
}
+1 -2
View File
@@ -13,8 +13,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
const isStandalone =
pathname === '/login' ||
pathname === '/register' ||
pathname?.startsWith('/install') ||
pathname?.startsWith('/admin');
pathname?.startsWith('/install');
if (loading) {
return (
+16 -1
View File
@@ -19,6 +19,7 @@ export function RightSidebar() {
bannerUrl: '',
admins: [] as Admin[],
});
const [version, setVersion] = useState<{ count: number | null; hash: string; fullHash: string | null } | null>(null);
const [loading, setLoading] = useState(true);
@@ -39,6 +40,12 @@ export function RightSidebar() {
})
.catch(() => { })
.finally(() => setLoading(false));
// Fetch version info
fetch('/api/version')
.then(res => res.json())
.then(data => setVersion(data))
.catch(() => setVersion({ count: null, hash: 'unknown', fullHash: null }));
}, []);
if (loading) {
@@ -108,7 +115,15 @@ export function RightSidebar() {
<div className="card" style={{ marginTop: '16px' }}>
<h3 style={{ fontWeight: 600, marginBottom: '12px' }}>Network Info</h3>
<p style={{ color: 'var(--foreground-secondary)', fontSize: '13px' }}>
Running <a href="https://synapsis.social" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Synapsis v0.1.0</a>
Running <a href="https://synapsis.social" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Synapsis</a>
{version && version.count !== null && (
<>
{' • '}
<span title={`${version.hash} (${version.fullHash})`}>
Commit {version.count}
</span>
</>
)}
</p>
{nodeInfo.admins.length > 0 && (