Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/app/admin/events/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

useEffect(() => {
fetchEvents();
}, [page, search, statusFilter]);

Check warning on line 64 in src/app/admin/events/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchEvents'. Either include it or remove the dependency array

Check warning on line 64 in src/app/admin/events/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchEvents'. Either include it or remove the dependency array

async function fetchEvents() {
setLoading(true);
Expand Down Expand Up @@ -300,7 +300,7 @@
<div className="text-sm text-text-tertiary">
Page {page} of {totalPages}
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
<Button
variant="secondary"
size="default"
Expand All @@ -309,6 +309,22 @@
>
<ChevronLeft className="w-4 h-4" />
</Button>
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => {
const start = Math.max(1, Math.min(page - 2, totalPages - 4));
const p = start + i;
if (p > totalPages) return null;
return (
<Button
key={p}
variant={p === page ? "default" : "ghost"}
size="default"
onClick={() => setPage(p)}
className="w-10"
>
{p}
</Button>
);
})}
<Button
variant="secondary"
size="default"
Expand Down
34 changes: 25 additions & 9 deletions src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
X,
Shield,
ChevronDown,
Lock,
} from "lucide-react";

interface NavItem {
Expand Down Expand Up @@ -47,10 +48,11 @@ export default function AdminLayout({
const userRole = user?.role || 'user';
const canAccessAdmin = user && hasPermission(userRole, 'moderator');

// Filter nav items based on user's role
const visibleNavItems = navItems.filter(item =>
user && hasPermission(userRole, item.requiredRole)
);
// Show all nav items, marking inaccessible ones
const visibleNavItems = navItems.map(item => ({
...item,
accessible: !!(user && hasPermission(userRole, item.requiredRole)),
}));

useEffect(() => {
if (!isLoading) {
Expand Down Expand Up @@ -89,14 +91,14 @@ export default function AdminLayout({
{/* Mobile sidebar overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
className="fixed inset-0 bg-black/50 z-40 md:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}

{/* Sidebar */}
<aside
className={`fixed top-0 left-0 z-50 h-full w-64 bg-surface border-r border-elevated transform transition-transform duration-200 lg:translate-x-0 ${
className={`fixed top-0 left-0 z-50 h-full w-64 bg-surface border-r border-elevated transform transition-transform duration-200 md:translate-x-0 ${
sidebarOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
Expand All @@ -111,7 +113,7 @@ export default function AdminLayout({
</Link>
<button
onClick={() => setSidebarOpen(false)}
className="lg:hidden p-2 hover:bg-elevated rounded-lg"
className="md:hidden p-2 hover:bg-elevated rounded-lg"
>
<X className="w-5 h-5" />
</button>
Expand All @@ -125,6 +127,20 @@ export default function AdminLayout({
(item.href !== "/admin" && pathname.startsWith(item.href));
const Icon = item.icon;

if (!item.accessible) {
return (
<div
key={item.href}
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-tertiary/50 cursor-not-allowed"
title={`Requires ${item.requiredRole} role`}
>
<Icon className="w-5 h-5" />
<span className="font-medium flex-1">{item.label}</span>
<Lock className="w-3.5 h-3.5" />
</div>
);
}

return (
<Link
key={item.href}
Expand Down Expand Up @@ -188,13 +204,13 @@ export default function AdminLayout({
</aside>

{/* Main content */}
<div className="lg:pl-64">
<div className="md:pl-64">
{/* Top bar */}
<header className="sticky top-0 z-30 h-16 bg-surface/80 backdrop-blur-sm border-b border-elevated">
<div className="h-full px-4 flex items-center gap-4">
<button
onClick={() => setSidebarOpen(true)}
className="lg:hidden p-2 hover:bg-elevated rounded-lg"
className="md:hidden p-2 hover:bg-elevated rounded-lg"
>
<Menu className="w-5 h-5" />
</button>
Expand Down
55 changes: 53 additions & 2 deletions src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Eye,
ArrowUpRight,
ArrowDownRight,
Loader2,

Check warning on line 13 in src/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'Loader2' is defined but never used

Check warning on line 13 in src/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'Loader2' is defined but never used
AlertCircle,
CheckCircle,
Clock,
Expand Down Expand Up @@ -72,7 +72,7 @@

useEffect(() => {
fetchDashboardData();
}, []);

Check warning on line 75 in src/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchDashboardData'. Either include it or remove the dependency array

Check warning on line 75 in src/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchDashboardData'. Either include it or remove the dependency array

async function fetchDashboardData() {
try {
Expand Down Expand Up @@ -128,8 +128,59 @@

if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<Loader2 className="w-8 h-8 animate-spin text-primary" />
<div className="space-y-6">
{/* Header skeleton */}
<div>
<div className="h-8 w-40 bg-elevated rounded animate-pulse" />
<div className="h-5 w-72 bg-elevated rounded animate-pulse mt-2" />
</div>
{/* Stat cards skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="w-10 h-10 rounded-lg bg-elevated animate-pulse" />
<div className="w-12 h-5 bg-elevated rounded animate-pulse" />
</div>
<div className="h-8 w-20 bg-elevated rounded animate-pulse mb-1" />
<div className="h-4 w-24 bg-elevated rounded animate-pulse" />
</CardContent>
</Card>
))}
</div>
{/* Content grid skeleton */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{Array.from({ length: 2 }).map((_, i) => (
<Card key={i}>
<CardHeader className="flex flex-row items-center justify-between">
<div className="h-6 w-32 bg-elevated rounded animate-pulse" />
<div className="h-4 w-16 bg-elevated rounded animate-pulse" />
</CardHeader>
<CardContent className="space-y-4">
{Array.from({ length: 3 }).map((_, j) => (
<div key={j} className="flex items-center gap-3 p-3 rounded-lg bg-elevated">
<div className="w-10 h-10 rounded-full bg-surface animate-pulse shrink-0" />
<div className="flex-1 space-y-2">
<div className="h-4 w-3/4 bg-surface rounded animate-pulse" />
<div className="h-3 w-1/2 bg-surface rounded animate-pulse" />
</div>
</div>
))}
</CardContent>
</Card>
))}
</div>
{/* Support tickets skeleton */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div className="h-6 w-36 bg-elevated rounded animate-pulse" />
<div className="h-4 w-16 bg-elevated rounded animate-pulse" />
</CardHeader>
<CardContent>
<div className="h-20 w-full bg-elevated rounded animate-pulse" />
</CardContent>
</Card>
</div>
);
}
Expand Down
49 changes: 49 additions & 0 deletions src/app/admin/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface Settings {

export default function SettingsPage() {
const [saving, setSaving] = useState(false);
const [showClearConfirm, setShowClearConfirm] = useState(false);
const [clearConfirmText, setClearConfirmText] = useState("");
const [settings, setSettings] = useState<Settings>({
siteName: "nhimbe",
supportEmail: "support@nhimbe.com",
Expand Down Expand Up @@ -194,6 +196,9 @@ export default function SettingsPage() {
<div className="text-sm text-text-tertiary">
Allow users to RSVP for events
</div>
<div className="text-xs text-text-tertiary mt-1">
When disabled, new registrations will be paused across all events
</div>
</div>
<Switch
checked={settings.enableRegistrations}
Expand All @@ -208,6 +213,9 @@ export default function SettingsPage() {
<div className="text-sm text-text-tertiary">
Allow users to leave reviews on past events
</div>
<div className="text-xs text-text-tertiary mt-1">
When disabled, users will not be able to submit or view reviews
</div>
</div>
<Switch
checked={settings.enableReviews}
Expand All @@ -222,6 +230,9 @@ export default function SettingsPage() {
<div className="text-sm text-text-tertiary">
Enable referral tracking and leaderboards
</div>
<div className="text-xs text-text-tertiary mt-1">
When disabled, referral codes and leaderboards will be hidden
</div>
</div>
<Switch
checked={settings.enableReferrals}
Expand Down Expand Up @@ -319,11 +330,49 @@ export default function SettingsPage() {
<Button
variant="ghost"
className="text-red-400 border border-red-500/20 hover:bg-red-500/10"
onClick={() => { setShowClearConfirm(true); setClearConfirmText(""); }}
>
<AlertTriangle className="w-4 h-4" />
Clear All Data
</Button>
</div>

{/* Clear All Data confirmation modal */}
{showClearConfirm && (
<div className="fixed inset-0 bg-black/50 flex items-end sm:items-center justify-center z-50 p-4 pb-[calc(1rem+env(safe-area-inset-bottom,0px))]">
<div className="bg-elevated rounded-2xl p-6 max-w-md w-full">
<h3 className="text-xl font-bold mb-2 text-red-400">Confirm Data Deletion</h3>
<p className="text-text-secondary mb-4">
This will permanently delete <strong>all events, users, and data</strong>. This action cannot be undone.
</p>
<p className="text-sm text-text-tertiary mb-3">
Type <strong>DELETE</strong> to confirm:
</p>
<Input
type="text"
value={clearConfirmText}
onChange={(e) => setClearConfirmText(e.target.value)}
placeholder="Type DELETE"
className="w-full px-4 py-3 bg-surface rounded-xl border-none outline-none mb-4 text-base"
autoFocus
/>
<div className="flex gap-3">
<button
onClick={() => setShowClearConfirm(false)}
className="flex-1 px-4 py-3 rounded-xl bg-surface hover:bg-foreground/10 font-medium transition-colors"
>
Cancel
</button>
<button
disabled={clearConfirmText !== "DELETE"}
className="flex-1 px-4 py-3 rounded-xl bg-red-500 text-white font-medium hover:bg-red-600 transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
>
Clear All Data
</button>
</div>
</div>
</div>
)}
</CardContent>
</Card>

Expand Down
4 changes: 2 additions & 2 deletions src/app/admin/signage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Users,
Calendar,
TrendingUp,
BarChart3,

Check warning on line 9 in src/app/admin/signage/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'BarChart3' is defined but never used

Check warning on line 9 in src/app/admin/signage/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'BarChart3' is defined but never used
Activity,
Shield,
Globe,
Expand Down Expand Up @@ -127,9 +127,9 @@
<p className="text-red-400">{error}</p>
) : code ? (
<>
<div className="flex justify-center gap-3 mb-6">
<div className="flex justify-center gap-2 sm:gap-3 mb-6">
{code.split("").map((char, i) => (
<div key={i} className="w-16 h-20 bg-white/10 rounded-xl flex items-center justify-center text-3xl font-mono font-bold border-2 border-white/20">
<div key={i} className="w-11 h-14 sm:w-16 sm:h-20 bg-white/10 rounded-xl flex items-center justify-center text-xl sm:text-3xl font-mono font-bold border-2 border-white/20">
{char}
</div>
))}
Expand Down
4 changes: 2 additions & 2 deletions src/app/admin/support/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
AlertCircle,
Clock,
CheckCircle,
MessageSquare,

Check warning on line 17 in src/app/admin/support/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'MessageSquare' is defined but never used

Check warning on line 17 in src/app/admin/support/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'MessageSquare' is defined but never used
Send,
X,
User,
Expand Down Expand Up @@ -66,7 +66,7 @@

useEffect(() => {
fetchTickets();
}, [page, search, statusFilter]);

Check warning on line 69 in src/app/admin/support/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchTickets'. Either include it or remove the dependency array

Check warning on line 69 in src/app/admin/support/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has a missing dependency: 'fetchTickets'. Either include it or remove the dependency array

async function fetchTickets() {
setLoading(true);
Expand Down Expand Up @@ -448,8 +448,8 @@
key={message.id}
className={`p-4 rounded-xl ${
message.sender === "admin"
? "bg-primary/10 ml-8"
: "bg-elevated mr-8"
? "bg-primary/10 ml-3 sm:ml-8"
: "bg-elevated mr-3 sm:mr-8"
}`}
>
<div className="flex items-center gap-2 mb-2">
Expand Down
Loading
Loading