diff --git a/package-lock.json b/package-lock.json index bb19d52..7989488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "esn-code-scanner", "version": "0.0.1", "dependencies": { + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.50.0", "@sveltejs/adapter-node": "^5.2.12" }, "devDependencies": { @@ -867,6 +869,101 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.70.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.70.0.tgz", + "integrity": "sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.10.tgz", + "integrity": "sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.6.1.tgz", + "integrity": "sha512-QtQgEMvaDzr77Mk3vZ3jWg2/y+D8tExYF7vcJT+wQ8ysuvOeGGjYbZlvj5bHYsj/SpC0bihcisnwPrM4Gp5G4g==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.43.4" + } + }, + "node_modules/@supabase/ssr/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.50.0.tgz", + "integrity": "sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.70.0", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.10", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", @@ -1265,12 +1362,36 @@ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.15.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.18.tgz", + "integrity": "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -2346,6 +2467,12 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2360,6 +2487,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2459,6 +2592,43 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index 5094ead..4282d0b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "vite": "^6.2.6" }, "dependencies": { + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.50.0", "@sveltejs/adapter-node": "^5.2.12" } } diff --git a/src/app.css b/src/app.css index 1c4d2a8..f173aa4 100644 --- a/src/app.css +++ b/src/app.css @@ -1,2 +1 @@ -@import 'tailwindcss'; -@plugin '@tailwindcss/typography'; +@import 'tailwindcss'; \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..9a0cf33 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,13 +1,21 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -// for information about these interfaces +import type { Session, SupabaseClient, User } from '@supabase/supabase-js' +import type { Database } from './database.types.ts' // import generated types + declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + interface Locals { + supabase: SupabaseClient + safeGetSession: () => Promise<{ session: Session | null; user: User | null }> + session: Session | null + user: User | null + } + interface PageData { + session: Session | null + } + // interface PageState {} + // interface Platform {} + } } -export {}; +export {} \ No newline at end of file diff --git a/src/app.html b/src/app.html index 77a5ff5..d2d0706 100644 --- a/src/app.html +++ b/src/app.html @@ -10,3 +10,11 @@
%sveltekit.body%
+ + + \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..079d393 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,81 @@ +import { createServerClient } from '@supabase/ssr' +import { type Handle, redirect } from '@sveltejs/kit' +import { sequence } from '@sveltejs/kit/hooks' + +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' + +const supabase: Handle = async ({ event, resolve }) => { + /** + * Creates a Supabase client specific to this server request. + * + * The Supabase client gets the Auth token from the request cookies. + */ + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + getAll: () => event.cookies.getAll(), + /** + * SvelteKit's cookies API requires `path` to be explicitly set in + * the cookie options. Setting `path` to `/` replicates previous/ + * standard behavior. + */ + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }) + }) + }, + }, + }) + + /** + * Unlike `supabase.auth.getSession()`, which returns the session _without_ + * validating the JWT, this function also calls `getUser()` to validate the + * JWT before returning the session. + */ + event.locals.safeGetSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + if (!session) { + return { session: null, user: null } + } + + const { + data: { user }, + error, + } = await event.locals.supabase.auth.getUser() + if (error) { + // JWT validation has failed + return { session: null, user: null } + } + + return { session, user } + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + /** + * Supabase libraries use the `content-range` and `x-supabase-api-version` + * headers, so we need to tell SvelteKit to pass it through. + */ + return name === 'content-range' || name === 'x-supabase-api-version' + }, + }) +} + +const authGuard: Handle = async ({ event, resolve }) => { + const { session, user } = await event.locals.safeGetSession() + event.locals.session = session + event.locals.user = user + + if (!event.locals.session && event.url.pathname.startsWith('/private')) { + redirect(303, '/auth') + } + + if (event.locals.session && event.url.pathname === '/auth') { + redirect(303, '/private/home') + } + + return resolve(event) +} + +export const handle: Handle = sequence(supabase, authGuard) \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..36fb4ed --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,31 @@ +export enum ScanState { + scanning, + scan_successful, + scan_failed +} + +export type TicketData = { + id: string; + name: string; + surname: string; + email: string; + event: { id: string; name: string }; + created_at: string; + created_by: { id: string; display_name: string } | null; + scanned: boolean; + scanned_at: string | null; + scanned_by: { id: string; display_name: string } | null; +}; + +export const defaultTicketData: TicketData = { + id: '', + name: '', + surname: '', + email: '', + event: '', + created_at: new Date().toISOString(), + created_by: null, + scanned: false, + scanned_at: null, + scanned_by: null, +}; \ No newline at end of file diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..7543dfa --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,9 @@ +import type { LayoutServerLoad } from './$types' + +export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => { + const { session } = await safeGetSession() + return { + session, + cookies: cookies.getAll(), + } +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b93e9ba..a8a75b4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,20 @@ - -{@render children()} +{@render children()} \ No newline at end of file diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..7a40abc --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,43 @@ +import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' +import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import type { LayoutLoad } from './$types' + +export const load: LayoutLoad = async ({ data, depends, fetch }) => { + /** + * Declare a dependency so the layout can be invalidated, for example, on + * session refresh. + */ + depends('supabase:auth') + + const supabase = isBrowser() + ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { + fetch, + }, + }) + : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { + fetch, + }, + cookies: { + getAll() { + return data.cookies + }, + }, + }) + + /** + * It's fine to use `getSession` here, because on the client, `getSession` is + * safe, and on the server, it reads `session` from the `LayoutData`, which + * safely checked the session using `safeGetSession`. + */ + const { + data: { session }, + } = await supabase.auth.getSession() + + const { + data: { user }, + } = await supabase.auth.getUser() + + return { session, supabase, user } +} \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4d9a80f..f229fc2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,68 +1,6 @@ - - -

CODE: {scanned_id}

- -

Name: {scan_data.name} {scan_data.surname}

-

State: {ticket_state}

-

Event: {scan_data.event_name}

+

hello world

diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte new file mode 100644 index 0000000..66f5f56 --- /dev/null +++ b/src/routes/auth/+page.svelte @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/routes/auth/confirm/+server.ts b/src/routes/auth/confirm/+server.ts new file mode 100644 index 0000000..4fd0574 --- /dev/null +++ b/src/routes/auth/confirm/+server.ts @@ -0,0 +1,31 @@ +import type { EmailOtpType } from '@supabase/supabase-js' +import { redirect } from '@sveltejs/kit' + +import type { RequestHandler } from './$types' + +export const GET: RequestHandler = async ({ url, locals: { supabase } }) => { + const token_hash = url.searchParams.get('token_hash') + const type = url.searchParams.get('type') as EmailOtpType | null + const next = url.searchParams.get('next') ?? '/' + + /** + * Clean up the redirect URL by deleting the Auth flow parameters. + * + * `next` is preserved for now, because it's needed in the error case. + */ + const redirectTo = new URL(url) + redirectTo.pathname = next + redirectTo.searchParams.delete('token_hash') + redirectTo.searchParams.delete('type') + + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ type, token_hash }) + if (!error) { + redirectTo.searchParams.delete('next') + redirect(303, redirectTo) + } + } + + redirectTo.pathname = '/auth/error' + redirect(303, redirectTo) +} \ No newline at end of file diff --git a/src/routes/auth/error/+page.svelte b/src/routes/auth/error/+page.svelte new file mode 100644 index 0000000..0176f4c --- /dev/null +++ b/src/routes/auth/error/+page.svelte @@ -0,0 +1 @@ +

Login error

\ No newline at end of file diff --git a/src/routes/auth/login/+page.server.ts b/src/routes/auth/login/+page.server.ts new file mode 100644 index 0000000..39c04e2 --- /dev/null +++ b/src/routes/auth/login/+page.server.ts @@ -0,0 +1,41 @@ +import { redirect } from '@sveltejs/kit' + +import type { Actions } from './$types' + +export const actions: Actions = { + signup: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + const display_name = formData.get('display_name') as string + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + display_name: display_name + } + } + }); + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/') + } + }, + login: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/private/home') + } + }, +} \ No newline at end of file diff --git a/src/routes/auth/login/+page.svelte b/src/routes/auth/login/+page.svelte new file mode 100644 index 0000000..518928c --- /dev/null +++ b/src/routes/auth/login/+page.svelte @@ -0,0 +1,11 @@ +
+ + + +
\ No newline at end of file diff --git a/src/routes/auth/signup/+page.server.ts b/src/routes/auth/signup/+page.server.ts new file mode 100644 index 0000000..39c04e2 --- /dev/null +++ b/src/routes/auth/signup/+page.server.ts @@ -0,0 +1,41 @@ +import { redirect } from '@sveltejs/kit' + +import type { Actions } from './$types' + +export const actions: Actions = { + signup: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + const display_name = formData.get('display_name') as string + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + display_name: display_name + } + } + }); + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/') + } + }, + login: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/private/home') + } + }, +} \ No newline at end of file diff --git a/src/routes/auth/signup/+page.svelte b/src/routes/auth/signup/+page.svelte new file mode 100644 index 0000000..4b9c71a --- /dev/null +++ b/src/routes/auth/signup/+page.svelte @@ -0,0 +1,15 @@ +
+ + + + +
\ No newline at end of file diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/private/+layout.server.ts b/src/routes/private/+layout.server.ts new file mode 100644 index 0000000..803cddb --- /dev/null +++ b/src/routes/private/+layout.server.ts @@ -0,0 +1,5 @@ +/** + * This file is necessary to ensure protection of all routes in the `private` + * directory. It makes the routes in this directory _dynamic_ routes, which + * send a server request, and thus trigger `hooks.server.ts`. + **/ \ No newline at end of file diff --git a/src/routes/private/+layout.svelte b/src/routes/private/+layout.svelte new file mode 100644 index 0000000..d3993d8 --- /dev/null +++ b/src/routes/private/+layout.svelte @@ -0,0 +1,15 @@ + + + + +
+ +
diff --git a/src/routes/private/home/+page.svelte b/src/routes/private/home/+page.svelte new file mode 100644 index 0000000..30acacb --- /dev/null +++ b/src/routes/private/home/+page.svelte @@ -0,0 +1,36 @@ + + +{#if user_data} + + +{:else} +

Loading user profile...

+{/if} diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte new file mode 100644 index 0000000..3bf1679 --- /dev/null +++ b/src/routes/private/scanner/+page.svelte @@ -0,0 +1,37 @@ + + + + + diff --git a/src/routes/QRScanner.svelte b/src/routes/private/scanner/QRScanner.svelte similarity index 90% rename from src/routes/QRScanner.svelte rename to src/routes/private/scanner/QRScanner.svelte index e85b5fb..e2f8d73 100644 --- a/src/routes/QRScanner.svelte +++ b/src/routes/private/scanner/QRScanner.svelte @@ -4,8 +4,7 @@ Html5QrcodeScanner, type Html5QrcodeResult, Html5QrcodeScanType, - Html5QrcodeSupportedFormats, - Html5QrcodeScannerState, + Html5QrcodeSupportedFormats } from 'html5-qrcode'; let width: number = 300; @@ -40,7 +39,7 @@ }); -
+