From b75758812762bb045a49ebfae8cadee70b039b9e Mon Sep 17 00:00:00 2001 From: victor-134 Date: Sun, 28 Jun 2026 16:55:05 +0100 Subject: [PATCH 1/5] feat: Add poll creation UI and backend integration --- src/app/api/polls/route.ts | 60 +++++++++++++++++++ src/components/CommandPalette.tsx | 40 +++++++++++-- .../collaboration/server/webSocketServer.ts | 5 ++ src/features/collaboration/types.ts | 11 ++++ 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 src/app/api/polls/route.ts diff --git a/src/app/api/polls/route.ts b/src/app/api/polls/route.ts new file mode 100644 index 00000000..ed647303 --- /dev/null +++ b/src/app/api/polls/route.ts @@ -0,0 +1,60 @@ +import { NextResponse } from 'next/server'; +import { query } from '@/lib/db/pool'; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const courseId = searchParams.get('course_id'); + + let polls; + if (courseId) { + const result = await query('SELECT * FROM polls WHERE course_id = $1 ORDER BY created_at DESC', [courseId]); + polls = result.rows; + } else { + const result = await query('SELECT * FROM polls ORDER BY created_at DESC LIMIT 100'); + polls = result.rows; + } + + return NextResponse.json({ data: polls }); + } catch (error) { + console.error('Failed to fetch polls:', error); + return NextResponse.json({ error: 'Failed to fetch polls' }, { status: 500 }); + } +} + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { id, question, options, course_id, created_by } = body; + + // Create table if it doesn't exist + await query(` + CREATE TABLE IF NOT EXISTS polls ( + id VARCHAR(255) PRIMARY KEY, + question TEXT NOT NULL, + options JSONB NOT NULL, + course_id VARCHAR(255), + created_by VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + ) + `); + + const result = await query( + \`INSERT INTO polls (id, question, options, course_id, created_by) + VALUES ($1, $2, $3, $4, $5) + RETURNING *\`, + [ + id || crypto.randomUUID(), + question, + JSON.stringify(options || []), + course_id || null, + created_by || 'anonymous' + ] + ); + + return NextResponse.json({ data: result.rows[0] }, { status: 201 }); + } catch (error) { + console.error('Failed to create poll:', error); + return NextResponse.json({ error: 'Failed to create poll' }, { status: 500 }); + } +} diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 7bf93d12..251a6dd6 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -5,6 +5,7 @@ import { PollCreationModal, type PollDraft } from '@/components/polls/PollCreati import { useSettingsStore } from '@/lib/settings/store'; import { useToast } from '@/context/ToastContext'; import { useTheme } from '@/lib/theme-provider'; +import { wsManager } from '@/lib/websocketManager'; import { type ShortcutActionId, type ShortcutCommand, @@ -85,6 +86,7 @@ function ShortcutRow({ export function CommandPalette() { const [open, setOpen] = useState(false); const [showHelp, setShowHelp] = useState(false); + const [isSubmittingPoll, setIsSubmittingPoll] = useState(false); const [query, setQuery] = useState(''); const { theme, setTheme } = useTheme(); const [pollModalOpen, setPollModalOpen] = useState(false); @@ -318,10 +320,40 @@ export function CommandPalette() { setPollModalOpen(false)} - onCreate={(draft: PollDraft) => { - // TODO: integrate with poll creation backend/GraphQL. - // For now, keep placeholder to satisfy typing and modal behavior. - console.log('Create poll draft', draft); + onCreate={async (draft: PollDraft) => { + if (isSubmittingPoll) return; + setIsSubmittingPoll(true); + + try { + const res = await fetch('/api/polls', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + question: draft.question, + options: draft.options.filter((option) => option.trim()), + durationDays: draft.durationDays, + allowAnonymous: draft.allowAnonymous, + resultsVisibility: draft.resultsVisibility, + }), + }); + + if (res.ok) { + const { data } = await res.json(); + const statuses = wsManager.getAllStatuses(); + const activeKey = Object.keys(statuses).find((key) => statuses[key].isConnected); + + if (activeKey) { + const socket = wsManager.getSocket(activeKey); + socket?.emit('collaboration:message', { + type: 'poll:created', + roomId: 'global', + poll: data, + }); + } + } + } finally { + setIsSubmittingPoll(false); + } }} /> diff --git a/src/features/collaboration/server/webSocketServer.ts b/src/features/collaboration/server/webSocketServer.ts index 3c1e0d90..84eb2336 100644 --- a/src/features/collaboration/server/webSocketServer.ts +++ b/src/features/collaboration/server/webSocketServer.ts @@ -83,6 +83,11 @@ export const setupCollaborationWebSocketServer = (httpServer: HttpServer): Socke return; } + if (message.type === 'poll:created' || message.type === 'poll:vote') { + socket.to(message.roomId).emit('collaboration:message', message); + return; + } + if (message.type === 'operation') { const roomState = getRoomState(message.roomId); diff --git a/src/features/collaboration/types.ts b/src/features/collaboration/types.ts index ed4a346f..87eedef4 100644 --- a/src/features/collaboration/types.ts +++ b/src/features/collaboration/types.ts @@ -66,4 +66,15 @@ export type CollaborationMessage = type: 'error'; roomId: string; message: string; + } + | { + type: 'poll:created'; + roomId: string; + poll: any; + } + | { + type: 'poll:vote'; + roomId: string; + pollId: string; + optionIndex: number; }; From d598e7b7f63c3e294c00011bf2fd6d5947f7732e Mon Sep 17 00:00:00 2001 From: victor-134 Date: Sun, 28 Jun 2026 18:55:22 +0100 Subject: [PATCH 2/5] Fix typescript and lint errors in CommandPalette --- src/components/CommandPalette.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 251a6dd6..7b7d3b74 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -351,6 +351,8 @@ export function CommandPalette() { }); } } + } catch { + toastInfo('Failed to submit poll.'); } finally { setIsSubmittingPoll(false); } From bd1e9074cddee45af5341ac6f14f14fb4f50ddfb Mon Sep 17 00:00:00 2001 From: victor-134 Date: Sun, 28 Jun 2026 18:59:33 +0100 Subject: [PATCH 3/5] Fix syntax error in route.ts --- src/app/api/polls/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/polls/route.ts b/src/app/api/polls/route.ts index ed647303..2df4f411 100644 --- a/src/app/api/polls/route.ts +++ b/src/app/api/polls/route.ts @@ -40,9 +40,9 @@ export async function POST(request: Request) { `); const result = await query( - \`INSERT INTO polls (id, question, options, course_id, created_by) + `INSERT INTO polls (id, question, options, course_id, created_by) VALUES ($1, $2, $3, $4, $5) - RETURNING *\`, + RETURNING *`, [ id || crypto.randomUUID(), question, From 633bd8d4bf3b25c3211c1b45f911aa027ba631cc Mon Sep 17 00:00:00 2001 From: victor-134 Date: Sun, 28 Jun 2026 19:06:16 +0100 Subject: [PATCH 4/5] fix: rewrite polls route.ts to eliminate hidden characters causing TS1127 --- src/app/api/polls/route.ts | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/app/api/polls/route.ts b/src/app/api/polls/route.ts index 2df4f411..c5ec48a1 100644 --- a/src/app/api/polls/route.ts +++ b/src/app/api/polls/route.ts @@ -5,16 +5,19 @@ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const courseId = searchParams.get('course_id'); - + let polls; if (courseId) { - const result = await query('SELECT * FROM polls WHERE course_id = $1 ORDER BY created_at DESC', [courseId]); + const result = await query( + 'SELECT * FROM polls WHERE course_id = $1 ORDER BY created_at DESC', + [courseId], + ); polls = result.rows; } else { const result = await query('SELECT * FROM polls ORDER BY created_at DESC LIMIT 100'); polls = result.rows; } - + return NextResponse.json({ data: polls }); } catch (error) { console.error('Failed to fetch polls:', error); @@ -26,32 +29,32 @@ export async function POST(request: Request) { try { const body = await request.json(); const { id, question, options, course_id, created_by } = body; - + // Create table if it doesn't exist - await query(` - CREATE TABLE IF NOT EXISTS polls ( - id VARCHAR(255) PRIMARY KEY, - question TEXT NOT NULL, - options JSONB NOT NULL, - course_id VARCHAR(255), - created_by VARCHAR(255), - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP - ) - `); - + await query( + 'CREATE TABLE IF NOT EXISTS polls (' + + 'id VARCHAR(255) PRIMARY KEY, ' + + 'question TEXT NOT NULL, ' + + 'options JSONB NOT NULL, ' + + 'course_id VARCHAR(255), ' + + 'created_by VARCHAR(255), ' + + 'created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP' + + ')', + ); + const result = await query( - `INSERT INTO polls (id, question, options, course_id, created_by) - VALUES ($1, $2, $3, $4, $5) - RETURNING *`, + 'INSERT INTO polls (id, question, options, course_id, created_by) ' + + 'VALUES ($1, $2, $3, $4, $5) ' + + 'RETURNING *', [ id || crypto.randomUUID(), question, JSON.stringify(options || []), course_id || null, - created_by || 'anonymous' - ] + created_by || 'anonymous', + ], ); - + return NextResponse.json({ data: result.rows[0] }, { status: 201 }); } catch (error) { console.error('Failed to create poll:', error); From c46afb30cbcab4afc229592b45e55a61aec86470 Mon Sep 17 00:00:00 2001 From: victor-134 Date: Mon, 29 Jun 2026 22:43:24 +0100 Subject: [PATCH 5/5] Fix pnpm workspace overrides config --- package.json | 17 ----------------- pnpm-workspace.yaml | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 95c7d5d1..bc40fed2 100644 --- a/package.json +++ b/package.json @@ -137,22 +137,5 @@ "typescript": "^5.8.3", "vite": "^6.4.3", "vitest": "^3.2.6" - }, - "pnpm": { - "auditConfig": { - "ignoreCves": [], - "ignoreGhsas": [] - }, - "overrides": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "next": "15.5.19", - "vite": ">=6.4.3", - "vitest": ">=3.2.6", - "ws": ">=8.21.0", - "typescript": "^5.8.3", - "eslint": "^9", - "prettier": "^2.8.8" - } } } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d9368ad8..1ec5a9ea 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,18 @@ packages: - . - packages/* + +auditConfig: + ignoreCves: [] + ignoreGhsas: [] + +overrides: + react: ^18.3.1 + react-dom: ^18.3.1 + next: 15.5.19 + vite: '>=6.4.3' + vitest: '>=3.2.6' + ws: '>=8.21.0' + typescript: ^5.8.3 + eslint: ^9 + prettier: ^2.8.8