add public routes for viewing qr code + switched using qr-code-styling for qr code generation

main
TZGyn 1 year ago
parent 338839974e
commit ee81c52172
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

Binary file not shown.

@ -18,4 +18,4 @@ primary_region = 'iad'
[[vm]] [[vm]]
size = 'shared-cpu-1x' size = 'shared-cpu-1x'
memory = '512mb' memory = '256mb'

@ -20,7 +20,6 @@
"@sveltejs/kit": "^2.5.5", "@sveltejs/kit": "^2.5.5",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/pg": "^8.11.6", "@types/pg": "^8.11.6",
"@types/qrcode": "^1.5.5",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"bun-types": "^1.0.11", "bun-types": "^1.0.11",
"drizzle-kit": "^0.23.1", "drizzle-kit": "^0.23.1",
@ -57,7 +56,7 @@
"oslo": "^1.2.0", "oslo": "^1.2.0",
"pg": "^8.11.5", "pg": "^8.11.5",
"postgres": "^3.4.3", "postgres": "^3.4.3",
"qrcode": "^1.5.3", "qr-code-styling": "^1.6.0-rc.1",
"resend": "^3.4.0", "resend": "^3.4.0",
"svelte-sonner": "^0.3.10", "svelte-sonner": "^0.3.10",
"sveltekit-superforms": "^2.12.2", "sveltekit-superforms": "^2.12.2",

@ -6,11 +6,9 @@ export const handle: Handle = async ({ event, resolve }) => {
const pathname = event.url.pathname const pathname = event.url.pathname
const protected_api = ['/api/logout']
if ( if (
pathname.startsWith('/dashboard') || pathname.startsWith('/dashboard') ||
protected_api.includes(pathname) pathname.startsWith('/api')
) { ) {
if (!sessionId) { if (!sessionId) {
redirect(303, '/login') redirect(303, '/login')

@ -10,6 +10,8 @@
<Sonner <Sonner
theme={$mode} theme={$mode}
expand
richColors
class="toaster group" class="toaster group"
toastOptions={{ toastOptions={{
classes: { classes: {

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import QRCode from 'qrcode'
import { Button } from '$lib/components/ui/button' import { Button } from '$lib/components/ui/button'
import { toast } from 'svelte-sonner' import { toast } from 'svelte-sonner'
import * as DropdownMenu from '$lib/components/ui/dropdown-menu' import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
import { Badge } from '$lib/components/ui/badge' import { Badge } from '$lib/components/ui/badge'
import QRCodeStyling from 'qr-code-styling'
export let background = '#fff' export let background = '#fff'
export let color = '#000' export let color = '#000'
@ -34,23 +34,28 @@
} }
async function generateQrCode() { async function generateQrCode() {
try { const qrcodestyling = new QRCodeStyling({
image = await QRCode.toDataURL(value, { data: value,
errorCorrectionLevel: 'L', width: 300,
margin: 1, height: 300,
scale: 20, margin: 10,
color: { qrOptions: {
light: background,
dark: color,
},
})
} catch (e) {
image = await QRCode.toDataURL(value, {
errorCorrectionLevel: 'L', errorCorrectionLevel: 'L',
margin: 1, typeNumber: 0,
scale: 20, },
}) backgroundOptions: {
} color: background,
},
dotsOptions: {
color: color,
},
cornersSquareOptions: {
type: 'square',
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return
image = URL.createObjectURL(blob)
} }
$: { $: {
@ -60,25 +65,28 @@
} }
</script> </script>
<div class="flex flex-col gap-4 items-center h-full"> <div class="flex h-full flex-col items-center gap-4">
<Badge variant="secondary"> <Badge variant="secondary">
{value} {value}
</Badge> </Badge>
<img src={image} alt={value} width={300} height={300} /> <img src={image} alt={value} width={300} height={300} />
<div class="flex gap-4 w-full"> <div class="flex w-full gap-4">
<Button class="w-full" on:click={copyImageToClipboard} <Button class="w-full" on:click={copyImageToClipboard}>
>Copy Image</Button> Copy Image
</Button>
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder> <DropdownMenu.Trigger asChild let:builder>
<Button builders={[builder]} class="w-full">QR Link</Button> <Button builders={[builder]} class="w-full">QR Link</Button>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content> <DropdownMenu.Content>
<DropdownMenu.Item href={`/url/${code}/qr`} target="_blank">
Standard
</DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
href={`/api/shortener/${code}/qr`} href={`/url/${code}/qr?color=true`}
target="_blank">Standard</DropdownMenu.Item> target="_blank">
<DropdownMenu.Item With Color
href={`/api/shortener/${code}/qr?color=true`} </DropdownMenu.Item>
target="_blank">With Color</DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</div> </div>

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import QRCode from 'qrcode'
import { Button } from '$lib/components/ui/button' import { Button } from '$lib/components/ui/button'
import { toast } from 'svelte-sonner' import { toast } from 'svelte-sonner'
import * as DropdownMenu from '$lib/components/ui/dropdown-menu' import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
import { Badge } from '$lib/components/ui/badge' import { Badge } from '$lib/components/ui/badge'
import QRCodeStyling from 'qr-code-styling'
export let background = '#fff' export let background = '#fff'
export let color = '#000' export let color = '#000'
@ -34,23 +34,28 @@
} }
async function generateQrCode() { async function generateQrCode() {
try { const qrcodestyling = new QRCodeStyling({
image = await QRCode.toDataURL(value, { data: value,
errorCorrectionLevel: 'L', width: 300,
margin: 1, height: 300,
scale: 20, margin: 10,
color: { qrOptions: {
light: background,
dark: color,
},
})
} catch (e) {
image = await QRCode.toDataURL(value, {
errorCorrectionLevel: 'L', errorCorrectionLevel: 'L',
margin: 1, typeNumber: 0,
scale: 20, },
}) backgroundOptions: {
} color: background,
},
dotsOptions: {
color: color,
},
cornersSquareOptions: {
type: 'square',
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return
image = URL.createObjectURL(blob)
} }
$: { $: {
@ -60,25 +65,28 @@
} }
</script> </script>
<div class="flex flex-col gap-4 items-center h-full"> <div class="flex h-full flex-col items-center gap-4">
<Badge variant="secondary"> <Badge variant="secondary">
{value} {value}
</Badge> </Badge>
<img src={image} alt={value} width={300} height={300} /> <img src={image} alt={value} width={300} height={300} />
<div class="flex gap-4 w-full"> <div class="flex w-full gap-4">
<Button class="w-full" on:click={copyImageToClipboard} <Button class="w-full" on:click={copyImageToClipboard}>
>Copy Image</Button> Copy Image
</Button>
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder> <DropdownMenu.Trigger asChild let:builder>
<Button builders={[builder]} class="w-full">QR Link</Button> <Button builders={[builder]} class="w-full">QR Link</Button>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content> <DropdownMenu.Content>
<DropdownMenu.Item href={`/url/${code}/qr`} target="_blank">
Standard
</DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
href={`/api/shortener/${code}/qr`} href={`/url/${code}/qr?color=true`}
target="_blank">Standard</DropdownMenu.Item> target="_blank">
<DropdownMenu.Item With Color
href={`/api/shortener/${code}/qr?color=true`} </DropdownMenu.Item>
target="_blank">With Color</DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</div> </div>

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from 'svelte' import { onMount } from 'svelte'
import QRCode from 'qrcode' import QRCodeStyling from 'qr-code-styling'
import { browser } from '$app/environment' import { browser } from '$app/environment'
export let background = '#fff' export let background = '#fff'
@ -15,15 +15,28 @@
} }
try { try {
image = await QRCode.toDataURL(value, { const qrcodestyling = new QRCodeStyling({
errorCorrectionLevel: 'L', data: value,
margin: 1, width: 300,
scale: 20, height: 300,
color: { margin: 10,
light: background, qrOptions: {
dark: color, errorCorrectionLevel: 'L',
typeNumber: 0,
},
backgroundOptions: {
color: background,
},
dotsOptions: {
color: color,
},
cornersSquareOptions: {
type: 'square',
}, },
}) })
const blob = await qrcodestyling.getRawData()
if (!blob) return
image = URL.createObjectURL(blob)
} catch (e) { } catch (e) {
image = '' image = ''
} }

@ -10,12 +10,14 @@
<p class="text-muted-foreground">Manage your account settings.</p> <p class="text-muted-foreground">Manage your account settings.</p>
</div> </div>
<Separator class="my-4 lg:my-6" /> <Separator class="my-4 lg:my-6" />
<div class="flex h-auto flex-col overflow-hidden lg:flex-row"> <div
class="flex h-auto w-full flex-col justify-center overflow-hidden lg:flex-row">
<aside <aside
class="flex-grow border-b pb-4 lg:max-w-[200px] lg:border-none"> class="flex-grow border-b pb-4 lg:max-w-[200px] lg:border-none">
<SidebarNav /> <SidebarNav />
</aside> </aside>
<div class="m-0 flex h-auto flex-grow overflow-hidden p-0"> <div
class="m-0 flex h-auto max-w-[600px] flex-grow overflow-hidden p-0">
<ScrollArea class="mt-0 w-full"> <ScrollArea class="mt-0 w-full">
<div class="w-full max-w-xl py-6 lg:px-8 lg:py-0"> <div class="w-full max-w-xl py-6 lg:px-8 lg:py-0">
<slot /> <slot />

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from 'svelte' import { onMount } from 'svelte'
import QRCode from 'qrcode' import QRCodeStyling from 'qr-code-styling'
import { browser } from '$app/environment' import { browser } from '$app/environment'
export let background = '#fff' export let background = '#fff'
@ -15,15 +15,28 @@
} }
try { try {
image = await QRCode.toDataURL(value, { const qrcodestyling = new QRCodeStyling({
errorCorrectionLevel: 'L', data: value,
margin: 1, width: 300,
scale: 20, height: 300,
color: { margin: 10,
light: background, qrOptions: {
dark: color, errorCorrectionLevel: 'L',
typeNumber: 0,
},
backgroundOptions: {
color: background,
},
dotsOptions: {
color: color,
},
cornersSquareOptions: {
type: 'square',
}, },
}) })
const blob = await qrcodestyling.getRawData()
if (!blob) return
image = URL.createObjectURL(blob)
} catch (e) { } catch (e) {
image = '' image = ''
} }

@ -1,6 +0,0 @@
import type { PageServerLoad } from './$types'
import { redirect } from '@sveltejs/kit'
export const load = (async () => {
return redirect(300, '/')
}) satisfies PageServerLoad

@ -0,0 +1,14 @@
<script>
import { goto } from '$app/navigation'
import Button from '$lib/components/ui/button/button.svelte'
</script>
<div class="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<div class="flex flex-col items-center gap-12">
<div class="flex flex-col items-center gap-4">
<div class="text-4xl font-bold">404</div>
<div class="text-4xl font-bold">Invalid Url</div>
</div>
<Button on:click={() => goto('/')} class="w-fit">Return</Button>
</div>
</div>

@ -1,17 +1,16 @@
import { db } from '$lib/db' import { db } from '$lib/db'
import type { RequestHandler } from './$types'
import QRCode from 'qrcode'
import { redirect } from '@sveltejs/kit' import { redirect } from '@sveltejs/kit'
import { env } from '$env/dynamic/public' import { env } from '$env/dynamic/public'
import type { PageServerLoad } from './$types'
const shortenerUrl = env.PUBLIC_SHORTENER_URL const shortenerUrl = env.PUBLIC_SHORTENER_URL
export const GET: RequestHandler = async (event) => { export const load = (async (event) => {
const shortenerId = event.params.id const { id } = event.params
const color = event.url.searchParams.get('color') const color = event.url.searchParams.get('color')
const shortener = await db.query.shortener.findFirst({ const shortener = await db.query.shortener.findFirst({
where: (shortener, { eq }) => eq(shortener.code, shortenerId), where: (shortener, { eq }) => eq(shortener.code, id),
with: { with: {
user: { user: {
with: { with: {
@ -23,23 +22,25 @@ export const GET: RequestHandler = async (event) => {
}) })
if (!shortener) { if (!shortener) {
redirect(303, '/') redirect(301, `/dashboard/links`)
} }
let colorSetting = {} let colorSetting: {
color: { background: string | null; foreground: string | null }
} | null = null
if (color === 'true') { if (color === 'true') {
if (shortener.project) { if (shortener.project) {
colorSetting = { colorSetting = {
color: { color: {
light: shortener.project.qr_background, background: shortener.project.qr_background,
dark: shortener.project.qr_foreground, foreground: shortener.project.qr_foreground,
}, },
} }
} else if (shortener.user.setting) { } else if (shortener.user.setting) {
colorSetting = { colorSetting = {
color: { color: {
light: shortener.user.setting.qr_background, background: shortener.user.setting.qr_background,
dark: shortener.user.setting.qr_foreground, foreground: shortener.user.setting.qr_foreground,
}, },
} }
} }
@ -50,17 +51,5 @@ export const GET: RequestHandler = async (event) => {
? shortener.project.custom_domain || shortenerUrl ? shortener.project.custom_domain || shortenerUrl
: shortenerUrl : shortenerUrl
const image = await QRCode.toBuffer(url + '/' + shortenerId, { return { shortener, url, colorSetting, shortenerId: id }
type: 'png', }) satisfies PageServerLoad
errorCorrectionLevel: 'L',
margin: 1,
scale: 20,
...colorSetting,
})
return new Response(image, {
headers: {
'Content-Type': 'image/png',
},
})
}

@ -0,0 +1,79 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button'
import { toast } from 'svelte-sonner'
import { Badge } from '$lib/components/ui/badge'
import QRCodeStyling from 'qr-code-styling'
import { onMount } from 'svelte'
import * as Card from '$lib/components/ui/card'
export let data
let image = ''
const copyImageToClipboard = async () => {
if (!image) return
const imageData = await fetch(image)
const imageBlob = await imageData.blob()
try {
navigator.clipboard.write([
new ClipboardItem({
'image/png': imageBlob,
}),
])
toast.success('Copied Image To Clipboard')
} catch (error) {
toast.error(
'Unable to copy item to clipboard. If you are using firefox, you can change the setting dom.events.asyncclipboard.clipboarditem in about:config to true',
)
}
}
async function generateQrCode() {
const qrcodestyling = new QRCodeStyling({
data: data.url + '/' + data.shortenerId,
width: 300,
height: 300,
margin: 10,
qrOptions: {
errorCorrectionLevel: 'L',
typeNumber: 0,
},
backgroundOptions: {
color: data.colorSetting?.color.background || '#fff',
},
dotsOptions: {
color: data.colorSetting?.color.foreground || '#000',
},
cornersSquareOptions: {
type: 'square',
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return
image = URL.createObjectURL(blob)
}
onMount(() => {
generateQrCode()
})
</script>
<Card.Root
class="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<Card.Header class="flex-col items-center gap-4">
<Badge variant="secondary" class="w-fit">
{data.url + '/' + data.shortenerId}
</Badge>
<img
src={image}
alt={data.url + '/' + data.shortenerId}
width={300}
height={300} />
<Button class="w-full" on:click={copyImageToClipboard}>
Copy Image
</Button>
</Card.Header>
</Card.Root>

@ -3,7 +3,7 @@
import Button from '$lib/components/ui/button/button.svelte' import Button from '$lib/components/ui/button/button.svelte'
</script> </script>
<div class="flex h-full w-full items-center justify-center"> <div class="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<div class="flex flex-col items-center gap-12"> <div class="flex flex-col items-center gap-12">
<div class="flex flex-col items-center gap-4"> <div class="flex flex-col items-center gap-4">
<div class="text-4xl font-bold">404</div> <div class="text-4xl font-bold">404</div>
Loading…
Cancel
Save