diff --git a/package-lock.json b/package-lock.json index 523a675..8ee9388 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "html2pdf.js": "^0.10.3", "mongoose": "^8.15.0", "next": "15.3.1", + "qrcode.react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -431,7 +433,6 @@ }, "node_modules/@babel/runtime": { "version": "7.27.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1552,11 +1553,25 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "license": "MIT" @@ -2076,6 +2091,18 @@ "node": ">= 0.4" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "dev": true, @@ -2223,6 +2250,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -2289,6 +2325,18 @@ "node": ">=16.20.1" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "dev": true, @@ -2381,6 +2429,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -2610,6 +2678,18 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/create-jest": { "version": "29.7.0", "dev": true, @@ -2660,6 +2740,15 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "dev": true, @@ -2848,6 +2937,16 @@ "license": "MIT", "peer": true }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dotenv": { "version": "16.5.0", "license": "BSD-2-Clause", @@ -3069,6 +3168,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/escalade": { "version": "3.2.0", "dev": true, @@ -3596,6 +3701,12 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -4001,6 +4112,30 @@ "dev": true, "license": "MIT" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2pdf.js": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.10.3.tgz", + "integrity": "sha512-RcB1sh8rs5NT3jgbN5zvvTmkmZrsUrxpZ/RI8TMbvuReNZAdJZG5TMfA2TBP6ZXxpXlWf9NB/ciLXVb6W2LbRQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.2.5", + "html2canvas": "^1.0.0", + "jspdf": "^3.0.0" + } + }, "node_modules/human-signals": { "version": "2.1.0", "dev": true, @@ -5412,6 +5547,24 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", + "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.7", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -6492,6 +6645,13 @@ "dev": true, "license": "MIT" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -6716,6 +6876,15 @@ ], "license": "MIT" }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -6735,6 +6904,16 @@ ], "license": "MIT" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "dev": true, @@ -6799,6 +6978,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "dev": true, @@ -6942,6 +7128,16 @@ "dev": true, "license": "MIT" }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -7365,6 +7561,16 @@ "node": ">=8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "engines": { @@ -7619,6 +7825,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "dev": true, @@ -7632,6 +7848,15 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.13", "dev": true, @@ -7957,6 +8182,15 @@ "punycode": "^2.1.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "dev": true, diff --git a/package.json b/package.json index c64ee64..51f5dd9 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,10 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "html2pdf.js": "^0.10.3", "mongoose": "^8.15.0", "next": "15.3.1", + "qrcode.react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/src/app/api/bookings/[id]/route.js b/src/app/api/bookings/[id]/route.js index e8ffa7e..bee6c68 100644 --- a/src/app/api/bookings/[id]/route.js +++ b/src/app/api/bookings/[id]/route.js @@ -1,7 +1,16 @@ +import connectDB from '@/lib/db/connectDB' import { NextResponse } from 'next/server' +import { getBookingById } from '@/lib/db/bookingDbService' import { updateBookingSeats, deleteBookingById } from '@/lib/db/bookingDbService' import { requireAdminAccess } from '@/lib/auth/requireAdminAccess' +export async function GET(req, { params }) { + const { id } = await params + await connectDB() + const booking = await getBookingById(id) + return NextResponse.json(booking) +} + export async function PATCH(req, context) { if (requireAdminAccess()) { return NextResponse.json({ error: 'Endast tillgängligt för administratörer' }, { status: 403 }) diff --git a/src/app/api/bookings/route.js b/src/app/api/bookings/route.js index a346ec8..150a0aa 100644 --- a/src/app/api/bookings/route.js +++ b/src/app/api/bookings/route.js @@ -1,7 +1,22 @@ +import connectDB from '@/lib/db/connectDB' import { NextResponse } from 'next/server' -import { getAllBookings } from '@/lib/db/bookingDbService' +import { getAllBookings, createBooking } from '@/lib/db/bookingDbService' import { requireAdminAccess } from '@/lib/auth/requireAdminAccess' +export async function POST(request) { + try { + await connectDB() + + const data = await request.json() + const savedBooking = await createBooking(data) + + return NextResponse.json(savedBooking, { status: 201 }) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'failed to create booking' }, { status: 500 }) + } +} + export async function GET() { if (requireAdminAccess()) { return NextResponse.json({ error: 'Endast tillgängligt för administratörer' }, { status: 403 }) diff --git a/src/app/api/screenings/[id]/route.js b/src/app/api/screenings/[id]/route.js index e341e0b..a4aafa3 100644 --- a/src/app/api/screenings/[id]/route.js +++ b/src/app/api/screenings/[id]/route.js @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server' import connectDB from '@/lib/db/connectDB' -import { deleteScreeningById, getScreeningById } from '@/lib/db/screeningDbService' +import { deleteScreeningById, getScreeningById, updateBookedSeats } from '@/lib/db/screeningDbService' import { requireAdminAccess } from '@/lib/auth/requireAdminAccess' export async function DELETE(req, context) { @@ -25,3 +25,21 @@ export async function GET(req, { params }) { const screening = await getScreeningById(id) return NextResponse.json(screening) } + +export async function PATCH(req, { params }) { + const { id } = await params + const { bookedSeats } = await req.json() + + try { + await connectDB() + + const updated = await updateBookedSeats(id, bookedSeats) + + if (!updated) { + return NextResponse.json({ error: 'Update to booked seats failed' }, { status: 404 }) + } + return NextResponse.json({ success: 'Updated booked seats' }, { status: 200 }) + } catch (err) { + return NextResponse.json({ error: err.message }, { status: 500 }) + } +} diff --git a/src/app/booking-confirmation/[id]/page.jsx b/src/app/booking-confirmation/[id]/page.jsx new file mode 100644 index 0000000..343d3a5 --- /dev/null +++ b/src/app/booking-confirmation/[id]/page.jsx @@ -0,0 +1,59 @@ +'use client' +import { use, useState, useEffect } from 'react' +import QrCodeGenerator from '@/components/booking-confirmation/QrCodeGenerator' +import ConfirmationDetails from '@/components/booking-confirmation/ConfirmationDetails' + +export default function BookingConfirmationPageId({ params }) { + const unwrappedParams = use(params) + + function handleDownloadPdf() { + const element = document.querySelector('.booking-confirmation__ticket-wrapper') + if (!element) return + + import('html2pdf.js').then((html2pdf) => { + html2pdf.default().from(element).save('bokning.pdf') + }) + } + + const [booking, setBooking] = useState(null) + + useEffect(() => { + async function fetchData() { + try { + const bookingRes = await fetch(`/api/bookings/${unwrappedParams.id}`) + const bookingData = await bookingRes.json() + + setBooking(bookingData) + } catch (error) { + console.error('Error fetching data from API:', error) + } + } + + fetchData() + }, [unwrappedParams.id]) + + if (!booking) return
Handling: {movie.plot}
++ Tid för visning: {formatedDate} kl: {formatedTime} Salong: + {screening.room.name} +
+ Rad: {row} +
++ Plats: {seatNumbers.join(', ')} +
+