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:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user