diff --git a/nest-front/src/App.tsx b/nest-front/src/App.tsx
index 46ddd48..8af3f33 100644
--- a/nest-front/src/App.tsx
+++ b/nest-front/src/App.tsx
@@ -3,7 +3,6 @@ import { Routes, Route } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import { ProtectedRoute } from './components/shared/ProtectedRoute';
import { PublicLayout } from './components/layout/PublicLayout';
-import { IntranetLayout } from './components/layout/IntranetLayout';
import { PageLoader } from './components/shared/PageLoader';
// ── Public Pages (lazy-loaded) ────────────────────────────────────────────────
@@ -20,15 +19,6 @@ const LoginPage = lazy(() => import('./pages/public/LoginPage'));
const RegisterPage = lazy(() => import('./pages/public/RegisterPage'));
const NotFoundPage = lazy(() => import('./pages/public/NotFoundPage'));
-// ── Intranet Pages (lazy-loaded) ──────────────────────────────────────────────
-
-const IntranetDashboard = lazy(() => import('./pages/intranet/IntranetDashboard'));
-const IntranetBugs = lazy(() => import('./pages/intranet/IntranetBugs'));
-const IntranetFeed = lazy(() => import('./pages/intranet/IntranetFeed'));
-const IntranetEvents = lazy(() => import('./pages/intranet/IntranetEvents'));
-const IntranetUsers = lazy(() => import('./pages/intranet/IntranetUsers'));
-const IntranetModeration = lazy(() => import('./pages/intranet/IntranetModeration'));
-
// ── App ────────────────────────────────────────────────────────────────────────
export default function App() {
@@ -57,23 +47,6 @@ export default function App() {
} />
} />
-
- {/* Intranet Routes — staff only */}
-
-
-
- }
- >
- } />
- } />
- } />
- } />
- } />
- } />
-
diff --git a/nest-front/src/components/layout/IntranetLayout.tsx b/nest-front/src/components/layout/IntranetLayout.tsx
deleted file mode 100644
index 52bf4a0..0000000
--- a/nest-front/src/components/layout/IntranetLayout.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import { NavLink, Outlet, useNavigate } from 'react-router-dom';
-import { useAuth } from '../../contexts/AuthContext';
-import { useCallback } from 'react';
-
-const INTRANET_LINKS = [
- { to: '/intranet', label: 'Dashboard', icon: '[>]', end: true },
- { to: '/intranet/bugs', label: 'Bug Reports', icon: '[!]', end: false },
- { to: '/intranet/feed', label: 'Team Feed', icon: '[~]', end: false },
- { to: '/intranet/events', label: 'Events', icon: '[E]', end: false },
- { to: '/intranet/users', label: 'Users', icon: '[U]', end: false },
- { to: '/intranet/moderation', label: 'Moderation', icon: '[M]', end: false },
-];
-
-export function IntranetLayout() {
- const { user, logout } = useAuth();
- const navigate = useNavigate();
-
- const handleLogout = useCallback(() => {
- logout();
- navigate('/');
- }, [logout, navigate]);
-
- return (
-
- {/* Sidebar */}
-
-
- {/* Main content */}
-
-
-
-
- );
-}
diff --git a/nest-front/src/components/shared/Navbar.tsx b/nest-front/src/components/shared/Navbar.tsx
index aa1b3d8..4d418d5 100644
--- a/nest-front/src/components/shared/Navbar.tsx
+++ b/nest-front/src/components/shared/Navbar.tsx
@@ -11,7 +11,7 @@ const NAV_LINKS = [
];
export function Navbar() {
- const { user, isAuthenticated, isStaff, logout } = useAuth();
+ const { user, isAuthenticated, logout } = useAuth();
const navigate = useNavigate();
const [menuOpen, setMenuOpen] = useState(false);
@@ -90,42 +90,6 @@ export function Navbar() {
{label}
))}
-
- {/* Intranet — visually separated, highlighted button */}
- {isStaff && (
- <>
- {/* Vertical divider */}
-
- ({
- fontFamily: 'var(--font-mono)',
- fontSize: '0.75rem',
- textTransform: 'uppercase',
- letterSpacing: '0.1em',
- textDecoration: 'none',
- background: isActive ? 'var(--color-yellow)' : 'var(--color-yellow)',
- color: 'var(--color-bg)',
- border: '2px solid var(--color-yellow)',
- padding: '0.2rem 0.65rem',
- fontWeight: 'bold',
- opacity: isActive ? 1 : 0.85,
- transition: 'opacity 0.1s',
- })}
- >
- ▮ INTRANET
-
- >
- )}
{/* Desktop Auth */}
@@ -230,32 +194,6 @@ export function Navbar() {
>
)}
-
- {/* Intranet button — mobile: separated at the bottom */}
- {isStaff && (
-
- ({
- display: 'inline-block',
- fontFamily: 'var(--font-mono)',
- fontSize: '0.8rem',
- textTransform: 'uppercase' as const,
- letterSpacing: '0.1em',
- textDecoration: 'none',
- background: 'var(--color-yellow)',
- color: 'var(--color-bg)',
- border: '2px solid var(--color-yellow)',
- padding: '0.3rem 0.85rem',
- fontWeight: 'bold',
- opacity: isActive ? 0.85 : 1,
- })}
- >
- ▮ INTRANET
-
-
- )}
)}
diff --git a/nest-front/src/pages/intranet/IntranetBugs.tsx b/nest-front/src/pages/intranet/IntranetBugs.tsx
deleted file mode 100644
index 05722bf..0000000
--- a/nest-front/src/pages/intranet/IntranetBugs.tsx
+++ /dev/null
@@ -1,251 +0,0 @@
-import { useState, useMemo, useCallback } from 'react';
-import { MOCK_BUGS, MOCK_USERS } from '../../data/mockData';
-import { useAuth } from '../../contexts/AuthContext';
-import { formatDate, formatDateTime } from '../../utils/format';
-import type { BugReport, BugSeverity, BugStatus, BugReportNote } from '../../types';
-
-function StatusBadge({ status }: { status: BugStatus }) {
- const map: Record = { open: 'badge-open', in_progress: 'badge-progress', resolved: 'badge-resolved', closed: 'badge-closed' };
- const labels: Record = { open: 'Open', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed' };
- return {labels[status]} ;
-}
-
-function SeverityBadge({ severity }: { severity: BugSeverity }) {
- const map: Record = { low: 'badge-low', medium: 'badge-medium', high: 'badge-high', critical: 'badge-critical' };
- return {severity} ;
-}
-
-const STATUSES: BugStatus[] = ['open', 'in_progress', 'resolved', 'closed'];
-const STAFF_MEMBERS = MOCK_USERS.filter((u) => u.role === 'dev' || u.role === 'com');
-
-export default function IntranetBugs() {
- const { user } = useAuth();
-
- const [bugs, setBugs] = useState(MOCK_BUGS);
- const [selected, setSelected] = useState(null);
- const [statusFilter, setStatusFilter] = useState('all');
- const [severityFilter, setSeverityFilter] = useState('all');
- const [assignedFilter, setAssignedFilter] = useState('all');
- const [noteText, setNoteText] = useState('');
-
- const openCount = bugs.filter((b) => b.status === 'open').length;
- const criticalCount = bugs.filter((b) => b.severity === 'critical').length;
- const myCount = bugs.filter((b) => b.assignedToId === user?.id).length;
-
- const filtered = useMemo(() => {
- return bugs.filter((b) => {
- if (statusFilter !== 'all' && b.status !== statusFilter) return false;
- if (severityFilter !== 'all' && b.severity !== severityFilter) return false;
- if (assignedFilter !== 'all') {
- if (assignedFilter === 'unassigned' && b.assignedToId) return false;
- if (assignedFilter !== 'unassigned' && b.assignedToId !== assignedFilter) return false;
- }
- return true;
- });
- }, [bugs, statusFilter, severityFilter, assignedFilter]);
-
- const updateBug = useCallback((id: string, changes: Partial) => {
- setBugs((prev) => prev.map((b) => b.id === id ? { ...b, ...changes, updatedAt: new Date().toISOString() } : b));
- setSelected((prev) => prev?.id === id ? { ...prev, ...changes, updatedAt: new Date().toISOString() } : prev);
- }, []);
-
- const handleAssign = useCallback((bugId: string, staffId: string) => {
- const staff = STAFF_MEMBERS.find((s) => s.id === staffId);
- updateBug(bugId, { assignedToId: staffId || undefined, assignedToName: staff?.username });
- }, [updateBug]);
-
- const handleStatusChange = useCallback((bugId: string, status: BugStatus) => {
- updateBug(bugId, { status });
- }, [updateBug]);
-
- const handleAddNote = useCallback((bugId: string) => {
- if (!noteText.trim() || !user) return;
- const note: BugReportNote = {
- id: `n${Date.now()}`,
- bugReportId: bugId,
- authorId: user.id,
- authorName: user.username,
- content: noteText.trim(),
- createdAt: new Date().toISOString(),
- };
- setBugs((prev) => prev.map((b) => b.id === bugId ? { ...b, notes: [...(b.notes ?? []), note] } : b));
- setSelected((prev) => prev?.id === bugId ? { ...prev, notes: [...(prev.notes ?? []), note] } : prev);
- setNoteText('');
- }, [noteText, user]);
-
- return (
-
- {/* Left panel */}
-
-
-
- INTRANET / BUG REPORTS
-
-
BUG DASHBOARD
-
- {/* Stats */}
-
- {[
- { label: 'Open', value: openCount, color: 'var(--color-green)' },
- { label: 'Critical', value: criticalCount, color: 'var(--color-red)' },
- { label: 'Mine', value: myCount, color: 'var(--color-amber)' },
- ].map(({ label, value, color }) => (
-
- ))}
-
-
- {/* Filters */}
-
- setStatusFilter(e.target.value as BugStatus | 'all')}>
- All Statuses
- {STATUSES.map((s) => {s} )}
-
- setSeverityFilter(e.target.value as BugSeverity | 'all')}>
- All Severities
- {(['critical','high','medium','low'] as BugSeverity[]).map((s) => {s} )}
-
- setAssignedFilter(e.target.value)}>
- All Assigned
- Unassigned
- {STAFF_MEMBERS.map((s) => {s.username} )}
-
-
-
-
- {/* Bug list */}
-
- {filtered.length === 0 ? (
-
- No reports match filters.
-
- ) : (
- filtered.map((bug) => (
-
setSelected(bug === selected ? null : bug)}
- style={{
- background: selected?.id === bug.id ? 'rgba(37,99,235,0.08)' : 'var(--color-surface)',
- border: `1px solid ${selected?.id === bug.id ? 'var(--color-yellow)' : 'var(--color-border)'}`,
- padding: '0.85rem 1.1rem',
- cursor: 'pointer',
- transition: 'all 0.15s',
- }}
- role="button"
- tabIndex={0}
- onKeyDown={(e) => e.key === 'Enter' && setSelected(bug === selected ? null : bug)}
- aria-label={`Select bug report ${bug.uniqueCode}`}
- >
-
-
- {bug.uniqueCode}
-
-
-
-
- {formatDate(bug.createdAt)}
-
-
-
- {bug.title}
-
-
- {bug.submittedByName} — Assigned: {bug.assignedToName ?? 'None'}
-
-
- ))
- )}
-
-
-
- {/* Right panel — detail */}
- {selected && (
-
-
-
-
{selected.uniqueCode}
-
{selected.title}
-
-
setSelected(null)} style={{ background: 'transparent', border: 'none', color: 'var(--color-text-muted)', cursor: 'pointer', fontSize: '1.1rem' }} aria-label="Close">✕
-
-
- {/* Controls */}
-
-
- STATUS
- handleStatusChange(selected.id, e.target.value as BugStatus)}
- >
- {STATUSES.map((s) => {s} )}
-
-
-
- ASSIGN TO
- handleAssign(selected.id, e.target.value)}
- >
- Unassigned
- {STAFF_MEMBERS.map((s) => {s.username} ({s.role}) )}
-
-
-
-
- {/* Description */}
-
-
DESCRIPTION
-
- {selected.description}
-
-
-
-
-
STEPS TO REPRODUCE
-
- {selected.stepsToReproduce}
-
-
-
- {/* Internal notes */}
-
-
INTERNAL NOTES (staff only)
- {(selected.notes ?? []).length === 0 ? (
-
No notes yet.
- ) : (
-
- {(selected.notes ?? []).map((note) => (
-
-
- {note.authorName} — {formatDateTime(note.createdAt)}
-
-
- {note.content}
-
-
- ))}
-
- )}
-
-
- )}
-
- );
-}
diff --git a/nest-front/src/pages/intranet/IntranetDashboard.tsx b/nest-front/src/pages/intranet/IntranetDashboard.tsx
deleted file mode 100644
index d996dbc..0000000
--- a/nest-front/src/pages/intranet/IntranetDashboard.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import { Link } from 'react-router-dom';
-import { useAuth } from '../../contexts/AuthContext';
-import { MOCK_BUGS, MOCK_STAFF_POSTS, MOCK_USERS, MOCK_THREADS } from '../../data/mockData';
-
-interface StatCardProps {
- label: string;
- value: number | string;
- accent?: 'green' | 'amber' | 'red';
-}
-
-function StatCard({ label, value, accent = 'green' }: StatCardProps) {
- const colors = {
- green: 'var(--color-green)',
- amber: 'var(--color-amber)',
- red: 'var(--color-red)',
- };
- return (
-
-
- {label}
-
-
- {value}
-
-
- );
-}
-
-interface NavTileProps {
- to: string;
- label: string;
- description: string;
- icon: string;
-}
-
-function NavTile({ to, label, description, icon }: NavTileProps) {
- return (
- {
- (e.currentTarget as HTMLAnchorElement).style.borderColor = 'var(--color-yellow)';
- (e.currentTarget as HTMLAnchorElement).style.background = 'rgba(37,99,235,0.05)';
- }}
- onMouseLeave={(e) => {
- (e.currentTarget as HTMLAnchorElement).style.borderColor = 'var(--color-border)';
- (e.currentTarget as HTMLAnchorElement).style.background = 'var(--color-surface)';
- }}
- >
-
- {icon}
-
-
- {label}
-
-
- {description}
-
-
- );
-}
-
-export default function IntranetDashboard() {
- const { user } = useAuth();
-
- const openBugs = MOCK_BUGS.filter((b) => b.status === 'open').length;
- const criticalBugs = MOCK_BUGS.filter((b) => b.severity === 'critical').length;
- const assignedToMe = MOCK_BUGS.filter((b) => b.assignedToId === user?.id).length;
- const totalUsers = MOCK_USERS.filter((u) => !u.isAdmin).length;
-
- return (
-
- {/* Header */}
-
-
- INTRANET / DASHBOARD
-
-
- Welcome, {user?.username}
-
-
- {new Date().toLocaleString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
-
-
-
- {/* Stats */}
-
-
- QUICK STATS
-
-
-
-
-
-
-
- p.createdAt.startsWith('2026-02-18')).length} accent="amber" />
-
-
-
- {/* Navigation tiles */}
-
-
- SECTIONS
-
-
-
-
-
-
-
-
-
- {/* Recent staff posts */}
-
-
- RECENT TEAM ACTIVITY
-
-
- {MOCK_STAFF_POSTS.slice(0, 4).map((post, idx) => (
-
-
-
- {post.authorName}
- [{post.authorRole}]
-
-
- {new Date(post.createdAt).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
-
-
-
- {post.content}
-
-
- ))}
-
-
-
- );
-}
diff --git a/nest-front/src/pages/intranet/IntranetEvents.tsx b/nest-front/src/pages/intranet/IntranetEvents.tsx
deleted file mode 100644
index 5fa3a20..0000000
--- a/nest-front/src/pages/intranet/IntranetEvents.tsx
+++ /dev/null
@@ -1,722 +0,0 @@
-import { useState, useCallback } from 'react';
-import { MOCK_EVENTS, MOCK_POLLS } from '../../data/mockData';
-import { useAuth } from '../../contexts/AuthContext';
-import { formatDateTime } from '../../utils/format';
-import type { EventPost, EventType, Poll, UserRole } from '../../types';
-
-const EVENT_TYPE_COLORS: Record = {
- announcement: 'var(--color-yellow)',
- update: 'var(--color-blue)',
- milestone: 'var(--color-green)',
- poll: 'var(--color-amber)',
-};
-
-const ROLE_COLORS: Record = {
- dev: 'var(--color-green)',
- com: 'var(--color-amber)',
- user: 'var(--color-text-muted)',
-};
-
-const EVENT_TYPE_LABELS: Record = {
- announcement: 'ANNOUNCEMENT',
- update: 'DEV UPDATE',
- milestone: 'MILESTONE',
- poll: 'COMMUNITY POLL',
-};
-
-// ── Poll Component ─────────────────────────────────────────────────────────────
-
-function PollCard({ poll, onVote }: { poll: Poll; onVote: (pollId: string, optionId: string) => void }) {
- const { user } = useAuth();
- const totalVotes = poll.options.reduce((sum, opt) => sum + opt.votes, 0);
- const isEnded = poll.endsAt ? new Date(poll.endsAt) < new Date() : false;
-
- return (
-
-
- {poll.question}
-
-
- {poll.options.map((option) => {
- const percentage = totalVotes > 0 ? Math.round((option.votes / totalVotes) * 100) : 0;
- const userVoted = option.votedUserIds.includes(user?.id || '');
-
- return (
-
{
- if (!isEnded && poll.isActive && user) {
- onVote(poll.id, option.id);
- }
- }}
- >
- {/* Progress bar */}
-
-
-
- {userVoted && '✓ '}
- {option.text}
-
-
- {option.votes} ({percentage}%)
-
-
-
- );
- })}
-
-
-
- {totalVotes} total votes
- {poll.allowMultipleVotes && ' • Multiple votes allowed'}
-
- {poll.endsAt && (
-
- {isEnded ? 'Poll Ended' : `Ends ${formatDateTime(poll.endsAt)}`}
-
- )}
-
-
- );
-}
-
-// ── Event Card Component ───────────────────────────────────────────────────────
-
-function EventCard({
- event,
- poll,
- onVote,
-}: {
- event: EventPost;
- poll?: Poll;
- onVote: (pollId: string, optionId: string) => void;
-}) {
- return (
-
- {/* Header */}
-
-
-
-
- {EVENT_TYPE_LABELS[event.type]}
-
- {event.isPublic && (
-
- PUBLIC
-
- )}
-
-
- {event.title}
-
-
-
- {event.authorName}
-
- •
- {formatDateTime(event.createdAt)}
-
-
-
-
- {/* Content */}
-
- {event.content}
-
-
- {/* Poll if exists */}
- {poll &&
}
-
- );
-}
-
-// ── Main Component ─────────────────────────────────────────────────────────────
-
-export default function IntranetEvents() {
- const { user } = useAuth();
- const [events, setEvents] = useState(MOCK_EVENTS);
- const [polls, setPolls] = useState(MOCK_POLLS);
- const [showCreateForm, setShowCreateForm] = useState(false);
-
- // Form state
- const [eventType, setEventType] = useState('announcement');
- const [title, setTitle] = useState('');
- const [content, setContent] = useState('');
- const [isPublic, setIsPublic] = useState(true);
- const [createPoll, setCreatePoll] = useState(false);
- const [pollQuestion, setPollQuestion] = useState('');
- const [pollOptions, setPollOptions] = useState(['', '']);
- const [error, setError] = useState('');
- const [posting, setPosting] = useState(false);
-
- const handleVote = useCallback(
- (pollId: string, optionId: string) => {
- if (!user) return;
-
- setPolls((prevPolls) =>
- prevPolls.map((poll) => {
- if (poll.id !== pollId) return poll;
-
- const hasVotedForOption = poll.options.some((opt) =>
- opt.votedUserIds.includes(user.id)
- );
-
- return {
- ...poll,
- options: poll.options.map((opt) => {
- if (opt.id === optionId) {
- // Add vote to this option
- return {
- ...opt,
- votes: opt.votedUserIds.includes(user.id) ? opt.votes : opt.votes + 1,
- votedUserIds: opt.votedUserIds.includes(user.id)
- ? opt.votedUserIds
- : [...opt.votedUserIds, user.id],
- };
- } else if (!poll.allowMultipleVotes && hasVotedForOption) {
- // Remove vote from other options if single vote
- return {
- ...opt,
- votes: opt.votedUserIds.includes(user.id) ? opt.votes - 1 : opt.votes,
- votedUserIds: opt.votedUserIds.filter((id) => id !== user.id),
- };
- }
- return opt;
- }),
- };
- })
- );
- },
- [user]
- );
-
- const handleSubmit = useCallback(async () => {
- // Validation
- if (!title.trim()) {
- setError('Title is required.');
- return;
- }
- if (!content.trim()) {
- setError('Content is required.');
- return;
- }
- if (createPoll) {
- if (!pollQuestion.trim()) {
- setError('Poll question is required.');
- return;
- }
- const validOptions = pollOptions.filter((opt) => opt.trim());
- if (validOptions.length < 2) {
- setError('Poll must have at least 2 options.');
- return;
- }
- }
- if (!user) return;
-
- setError('');
- setPosting(true);
- await new Promise((r) => setTimeout(r, 300));
-
- const newEventId = `evt${Date.now()}`;
- let newPollId: string | undefined;
-
- // Create poll if needed
- if (createPoll) {
- newPollId = `poll${Date.now()}`;
- const validOptions = pollOptions.filter((opt) => opt.trim());
- const newPoll: Poll = {
- id: newPollId,
- eventId: newEventId,
- question: pollQuestion.trim(),
- options: validOptions.map((opt, idx) => ({
- id: `opt${Date.now()}_${idx}`,
- text: opt.trim(),
- votes: 0,
- votedUserIds: [],
- })),
- isActive: true,
- allowMultipleVotes: false,
- createdAt: new Date().toISOString(),
- };
- setPolls((prev) => [newPoll, ...prev]);
- }
-
- // Create event
- const newEvent: EventPost = {
- id: newEventId,
- type: createPoll ? 'poll' : eventType,
- title: title.trim(),
- content: content.trim(),
- authorId: user.id,
- authorName: user.username,
- authorRole: user.role,
- createdAt: new Date().toISOString(),
- isPublic,
- pollId: newPollId,
- };
-
- setEvents((prev) => [newEvent, ...prev]);
-
- // Reset form
- setTitle('');
- setContent('');
- setEventType('announcement');
- setIsPublic(true);
- setCreatePoll(false);
- setPollQuestion('');
- setPollOptions(['', '']);
- setPosting(false);
- setShowCreateForm(false);
- }, [title, content, eventType, isPublic, createPoll, pollQuestion, pollOptions, user]);
-
- return (
-
-
-
- INTRANET / EVENTS
-
-
- COMMUNITY EVENTS
-
-
- Post game development updates, announcements, and community polls. Public events are
- visible to all users.
-
-
-
- {/* Create Event Button */}
- {!showCreateForm && (
-
setShowCreateForm(true)}
- style={{ marginBottom: '1.5rem' }}
- >
- + Create New Event
-
- )}
-
- {/* Create Event Form */}
- {showCreateForm && (
-
-
- CREATE NEW EVENT
-
-
- {/* Event Type */}
-
-
- EVENT TYPE
-
- setEventType(e.target.value as EventType)}
- style={{ fontSize: '0.8rem' }}
- disabled={createPoll}
- >
- Announcement
- Development Update
- Milestone
-
-
-
- {/* Title */}
-
-
- TITLE
-
- setTitle(e.target.value)}
- style={{ fontSize: '0.85rem' }}
- />
-
-
- {/* Content */}
-
-
- CONTENT
-
-
-
- {/* Public Toggle */}
-
-
- setIsPublic(e.target.checked)}
- style={{ cursor: 'pointer' }}
- />
- Make event visible to public
-
-
-
- {/* Poll Toggle */}
-
-
- setCreatePoll(e.target.checked)}
- style={{ cursor: 'pointer' }}
- />
- Include a community poll
-
-
-
- {/* Poll Form */}
- {createPoll && (
-
-
- POLL DETAILS
-
-
- {/* Poll Question */}
-
-
- QUESTION
-
- setPollQuestion(e.target.value)}
- style={{ fontSize: '0.8rem' }}
- />
-
-
- {/* Poll Options */}
-
-
- OPTIONS
-
- {pollOptions.map((option, idx) => (
-
- {
- const newOptions = [...pollOptions];
- newOptions[idx] = e.target.value;
- setPollOptions(newOptions);
- }}
- style={{ fontSize: '0.8rem', flex: 1 }}
- />
- {pollOptions.length > 2 && (
- {
- setPollOptions(pollOptions.filter((_, i) => i !== idx));
- }}
- style={{ padding: '0.4rem 0.6rem', fontSize: '0.7rem' }}
- >
- ×
-
- )}
-
- ))}
- {pollOptions.length < 6 && (
-
setPollOptions([...pollOptions, ''])}
- style={{ fontSize: '0.7rem', marginTop: '0.4rem' }}
- >
- + Add Option
-
- )}
-
-
- )}
-
- {/* Error */}
- {error && (
-
- {error}
-
- )}
-
- {/* Actions */}
-
-
- {posting ? 'Creating...' : '> Create Event'}
-
- {
- setShowCreateForm(false);
- setError('');
- setTitle('');
- setContent('');
- setCreatePoll(false);
- setPollQuestion('');
- setPollOptions(['', '']);
- }}
- disabled={posting}
- >
- Cancel
-
-
-
- )}
-
- {/* Events List */}
-
- {events.map((event) => {
- const poll = event.pollId ? polls.find((p) => p.id === event.pollId) : undefined;
- return ;
- })}
-
-
- );
-}
diff --git a/nest-front/src/pages/intranet/IntranetFeed.tsx b/nest-front/src/pages/intranet/IntranetFeed.tsx
deleted file mode 100644
index 599f78b..0000000
--- a/nest-front/src/pages/intranet/IntranetFeed.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { useState, useCallback } from 'react';
-import { MOCK_STAFF_POSTS } from '../../data/mockData';
-import { useAuth } from '../../contexts/AuthContext';
-import { formatDateTime } from '../../utils/format';
-import type { StaffPost, UserRole } from '../../types';
-
-const ROLE_COLORS: Record = {
- dev: 'var(--color-green)',
- com: 'var(--color-amber)',
- user: 'var(--color-text-muted)',
-};
-
-function FeedPost({ post }: { post: StaffPost }) {
- return (
-
-
-
-
- {post.authorName[0].toUpperCase()}
-
-
-
- {post.authorName}
-
-
- {post.authorRole}
-
-
-
-
- {formatDateTime(post.createdAt)}
-
-
-
- {post.content}
-
-
- );
-}
-
-export default function IntranetFeed() {
- const { user } = useAuth();
- const [posts, setPosts] = useState(MOCK_STAFF_POSTS);
- const [content, setContent] = useState('');
- const [error, setError] = useState('');
- const [posting, setPosting] = useState(false);
-
- const handlePost = useCallback(async () => {
- if (!content.trim()) { setError('Post cannot be empty.'); return; }
- if (content.trim().length < 5) { setError('Post must be at least 5 characters.'); return; }
- if (!user) return;
- setError('');
- setPosting(true);
- await new Promise((r) => setTimeout(r, 250));
-
- const newPost: StaffPost = {
- id: `sp${Date.now()}`,
- authorId: user.id,
- authorName: user.username,
- authorRole: user.role as 'dev' | 'com',
- content: content.trim(),
- createdAt: new Date().toISOString(),
- };
-
- setPosts((prev) => [newPost, ...prev]);
- setContent('');
- setPosting(false);
- }, [content, user]);
-
- return (
-
-
-
- INTRANET / TEAM FEED
-
-
TEAM ACTIVITY
-
- Staff-only internal feed. Posts are not visible to the public.
-
-
-
- {/* Compose */}
-
-
- [{user?.username} — {user?.role}] Post an update
-
-
-
- {/* Feed */}
-
- {posts.map((post) => (
-
- ))}
-
-
- );
-}
diff --git a/nest-front/src/pages/intranet/IntranetModeration.tsx b/nest-front/src/pages/intranet/IntranetModeration.tsx
deleted file mode 100644
index 0a74f60..0000000
--- a/nest-front/src/pages/intranet/IntranetModeration.tsx
+++ /dev/null
@@ -1,231 +0,0 @@
-import { useState, useMemo, useCallback } from 'react';
-import { MOCK_THREADS, MOCK_REPLIES } from '../../data/mockData';
-import { formatDateTime } from '../../utils/format';
-import type { ForumThread, ForumReply } from '../../types';
-
-export default function IntranetModeration() {
- const [threads, setThreads] = useState(MOCK_THREADS);
- const [replies, setReplies] = useState(MOCK_REPLIES);
- const [selectedThreadId, setSelectedThreadId] = useState(null);
- const [search, setSearch] = useState('');
- const [activeTab, setActiveTab] = useState<'threads' | 'replies'>('threads');
-
- const filteredThreads = useMemo(() => {
- if (!search.trim()) return threads;
- const q = search.toLowerCase();
- return threads.filter((t) => t.title.toLowerCase().includes(q) || t.authorName.toLowerCase().includes(q));
- }, [threads, search]);
-
- const selectedThreadReplies = useMemo(() => {
- if (!selectedThreadId) return [];
- return replies.filter((r) => r.threadId === selectedThreadId);
- }, [replies, selectedThreadId]);
-
- const deleteThread = useCallback((id: string) => {
- setThreads((prev) => prev.filter((t) => t.id !== id));
- setReplies((prev) => prev.filter((r) => r.threadId !== id));
- if (selectedThreadId === id) setSelectedThreadId(null);
- }, [selectedThreadId]);
-
- const togglePin = useCallback((id: string) => {
- setThreads((prev) => prev.map((t) => t.id === id ? { ...t, isPinned: !t.isPinned } : t));
- }, []);
-
- const toggleLock = useCallback((id: string) => {
- setThreads((prev) => prev.map((t) => t.id === id ? { ...t, isLocked: !t.isLocked } : t));
- }, []);
-
- const deleteReply = useCallback((id: string) => {
- setReplies((prev) => prev.filter((r) => r.id !== id));
- }, []);
-
- const recentReplies = useMemo(() => {
- return [...replies].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 20);
- }, [replies]);
-
- return (
-
-
-
- INTRANET / MODERATION
-
-
FORUM MODERATION
-
- {threads.length} threads — {replies.length} replies
-
-
-
- {/* Tabs */}
-
- {(['threads', 'replies'] as const).map((tab) => (
- setActiveTab(tab)}
- style={{
- background: 'transparent',
- border: 'none',
- borderBottom: activeTab === tab ? '2px solid var(--color-amber)' : '2px solid transparent',
- color: activeTab === tab ? 'var(--color-amber)' : 'var(--color-text-muted)',
- fontFamily: 'var(--font-mono)',
- fontSize: '0.75rem',
- padding: '0.55rem 1rem',
- cursor: 'pointer',
- textTransform: 'uppercase',
- letterSpacing: '0.08em',
- }}
- >
- {tab === 'threads' ? `Threads (${threads.length})` : `Replies (${replies.length})`}
-
- ))}
-
-
- {activeTab === 'threads' && (
-
- {/* Thread list */}
-
-
setSearch(e.target.value)}
- style={{ marginBottom: '1rem', maxWidth: '300px' }}
- />
-
- {filteredThreads.map((thread) => (
-
-
-
-
- {thread.isPinned && Pinned }
- {thread.isLocked && Locked }
-
-
{thread.title}
-
- by {thread.authorName} — {thread.categoryName} — {thread.replyCount} replies
-
-
-
-
-
- setSelectedThreadId(selectedThreadId === thread.id ? null : thread.id)}
- >
- Replies
-
- togglePin(thread.id)}
- >
- {thread.isPinned ? 'Unpin' : 'Pin'}
-
- toggleLock(thread.id)}
- >
- {thread.isLocked ? 'Unlock' : 'Lock'}
-
- deleteThread(thread.id)}
- >
- Delete
-
-
-
- ))}
- {filteredThreads.length === 0 && (
-
- No threads found.
-
- )}
-
-
-
- {/* Thread replies panel */}
- {selectedThreadId && (
-
-
-
- REPLIES ({selectedThreadReplies.length})
-
-
setSelectedThreadId(null)} style={{ background: 'transparent', border: 'none', color: 'var(--color-text-muted)', cursor: 'pointer' }} aria-label="Close">✕
-
- {selectedThreadReplies.length === 0 ? (
-
No replies.
- ) : (
-
- {selectedThreadReplies.map((reply) => (
-
-
- {reply.authorName}
- deleteReply(reply.id)}
- >
- Delete
-
-
-
- {reply.content.slice(0, 150)}{reply.content.length > 150 ? '...' : ''}
-
-
- ))}
-
- )}
-
- )}
-
- )}
-
- {activeTab === 'replies' && (
-
- {recentReplies.map((reply) => {
- const thread = threads.find((t) => t.id === reply.threadId);
- return (
-
-
-
-
- by {reply.authorName}
- {thread && <> in {thread.title} >}
- {' '}— {formatDateTime(reply.createdAt)}
-
-
- {reply.content}
-
-
-
deleteReply(reply.id)}
- >
- Delete
-
-
-
- );
- })}
- {recentReplies.length === 0 && (
-
- No replies found.
-
- )}
-
- )}
-
- );
-}
diff --git a/nest-front/src/pages/intranet/IntranetUsers.tsx b/nest-front/src/pages/intranet/IntranetUsers.tsx
deleted file mode 100644
index 44c1cdb..0000000
--- a/nest-front/src/pages/intranet/IntranetUsers.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { useState, useMemo, useCallback } from 'react';
-import { MOCK_USERS, MOCK_THREADS, MOCK_BUGS } from '../../data/mockData';
-import { useAuth } from '../../contexts/AuthContext';
-import { formatDate } from '../../utils/format';
-import type { User, UserRole } from '../../types';
-
-export default function IntranetUsers() {
- const { user: currentUser } = useAuth();
- const [users, setUsers] = useState(MOCK_USERS);
- const [search, setSearch] = useState('');
- const [roleFilter, setRoleFilter] = useState('all');
- const [confirmAction, setConfirmAction] = useState<{ userId: string; action: 'promote' | 'ban' | 'unban' } | null>(null);
-
- const threadCounts = useMemo(() => {
- const map: Record = {};
- MOCK_THREADS.forEach((t) => { map[t.authorId] = (map[t.authorId] ?? 0) + 1; });
- return map;
- }, []);
-
- const bugCounts = useMemo(() => {
- const map: Record = {};
- MOCK_BUGS.forEach((b) => { map[b.submittedById] = (map[b.submittedById] ?? 0) + 1; });
- return map;
- }, []);
-
- const filtered = useMemo(() => {
- return users.filter((u) => {
- const matchSearch = !search.trim() || u.username.toLowerCase().includes(search.toLowerCase()) || u.email.toLowerCase().includes(search.toLowerCase());
- const matchRole = roleFilter === 'all' || u.role === roleFilter;
- return matchSearch && matchRole;
- });
- }, [users, search, roleFilter]);
-
- const handlePromote = useCallback((userId: string, targetRole: UserRole) => {
- setUsers((prev) => prev.map((u) => u.id === userId ? { ...u, role: targetRole } : u));
- setConfirmAction(null);
- }, []);
-
- const handleToggleBan = useCallback((userId: string, ban: boolean) => {
- setUsers((prev) => prev.map((u) => u.id === userId ? { ...u, isBanned: ban } : u));
- setConfirmAction(null);
- }, []);
-
- return (
-
-
-
- INTRANET / USER MANAGEMENT
-
-
USERS
-
- {/* Filters */}
-
- setSearch(e.target.value)}
- style={{ maxWidth: '260px' }}
- />
- setRoleFilter(e.target.value as UserRole | 'all')}>
- All Roles
- Users
- Dev
- Com
-
-
-
-
- {/* Confirm dialog */}
- {confirmAction && (
-
setConfirmAction(null)}>
-
e.stopPropagation()}>
-
CONFIRM ACTION
-
- {confirmAction.action === 'promote'
- ? `Promote this user to staff? They will gain access to the intranet.`
- : confirmAction.action === 'ban'
- ? `Ban this user? They will be unable to login.`
- : `Unban this user? They will regain access to their account.`}
-
-
- {
- if (confirmAction.action === 'promote') handlePromote(confirmAction.userId, 'dev');
- else if (confirmAction.action === 'ban') handleToggleBan(confirmAction.userId, true);
- else handleToggleBan(confirmAction.userId, false);
- }}
- >
- Confirm
-
- setConfirmAction(null)}>Cancel
-
-
-
- )}
-
- {/* Table */}
-
-
-
-
- {['Username', 'Email', 'Role', 'Joined', 'Threads', 'Bugs', 'Status', 'Actions'].map((h) => (
-
- {h}
-
- ))}
-
-
-
- {filtered.map((u) => {
- const isSelf = u.id === currentUser?.id;
- return (
-
-
- {u.username} {isSelf && (you) }
- {u.isAdmin && [admin] }
-
- {u.email}
-
-
- {u.role}
-
-
- {formatDate(u.createdAt)}
- {threadCounts[u.id] ?? 0}
- {bugCounts[u.id] ?? 0}
-
- {u.isBanned ? (
- Banned
- ) : (
- Active
- )}
-
-
- {!isSelf && !u.isAdmin && (
-
- {u.role === 'user' && currentUser?.isAdmin && (
- setConfirmAction({ userId: u.id, action: 'promote' })}
- >
- Promote
-
- )}
- {u.isBanned ? (
- setConfirmAction({ userId: u.id, action: 'unban' })}
- >
- Unban
-
- ) : (
- setConfirmAction({ userId: u.id, action: 'ban' })}
- >
- Ban
-
- )}
-
- )}
-
-
- );
- })}
-
-
-
- {filtered.length === 0 && (
-
- No users match the current filters.
-
- )}
-
-
- );
-}