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
10 changes: 9 additions & 1 deletion apps/api/src/controllers/boards.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Board, Company, Post } from '../models/index.js';
import { asyncHandler, AppError } from '../middlewares/index.js';

export const listBoards = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const { boardID } = req.body;

// Get companyID from authenticated user first, then subdomain/context
let companyID: mongoose.Types.ObjectId;

Expand All @@ -24,9 +26,15 @@ export const listBoards = asyncHandler(async (req: Request, res: Response): Prom
companyID = defaultCompany._id;
}

// Build match query - filter by boardID if provided
const matchQuery: any = { companyID: new mongoose.Types.ObjectId(companyID) };
if (boardID) {
matchQuery._id = new mongoose.Types.ObjectId(boardID);
}

// Get boards with dynamic postCount via aggregation
const boards = await Board.aggregate([
{ $match: { companyID: new mongoose.Types.ObjectId(companyID) } },
{ $match: matchQuery },
{
$lookup: {
from: 'posts',
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/middlewares/validate.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const cursorPaginationSchema = z.object({
// Board schemas
export const boardListSchema = z.object({
apiKey: z.string().optional(),
boardID: objectIdSchema.optional(),
});

export const boardRetrieveSchema = z.object({
Expand Down
28 changes: 28 additions & 0 deletions apps/web/src/pages/admin/AdminBoardsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
TrashIcon,
EyeIcon,
EyeSlashIcon,
ClipboardIcon,
CheckIcon,
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';

Expand All @@ -20,6 +22,18 @@ export function AdminBoardsPage() {
const [editingBoard, setEditingBoard] = useState<{ id: string; name: string; isPrivate: boolean } | null>(null);
const [newBoard, setNewBoard] = useState<CreateBoardData>({ name: '', isPrivate: false });
const [isSubmitting, setIsSubmitting] = useState(false);
const [copiedBoardId, setCopiedBoardId] = useState<string | null>(null);

const handleCopyBoardId = async (boardId: string) => {
try {
await navigator.clipboard.writeText(boardId);
setCopiedBoardId(boardId);
toast.success('Board ID copied to clipboard!');
setTimeout(() => setCopiedBoardId(null), 2000);
} catch (err) {
toast.error('Failed to copy Board ID');
}
};

useEffect(() => {
fetchBoards();
Expand Down Expand Up @@ -181,6 +195,18 @@ export function AdminBoardsPage() {
</td>
<td className="of-px-6 of-py-4 of-whitespace-nowrap of-text-right">
<div className="of-flex of-items-center of-justify-end of-gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => handleCopyBoardId(board.id)}
title="Copy Board ID"
>
{copiedBoardId === board.id ? (
<CheckIcon className="of-w-4 of-h-4 of-text-green-600" />
) : (
<ClipboardIcon className="of-w-4 of-h-4" />
)}
</Button>
<Button
size="sm"
variant="ghost"
Expand All @@ -189,6 +215,7 @@ export function AdminBoardsPage() {
name: board.name,
isPrivate: board.isPrivate,
})}
title="Edit Board"
>
<PencilIcon className="of-w-4 of-h-4" />
</Button>
Expand All @@ -197,6 +224,7 @@ export function AdminBoardsPage() {
variant="ghost"
onClick={() => handleDelete(board.id, board.name)}
className="of-text-red-600 hover:of-text-red-700"
title="Delete Board"
>
<TrashIcon className="of-w-4 of-h-4" />
</Button>
Expand Down