added qr color customization

pull/3/head
TZGyn 2 years ago
parent 9cb2243c00
commit f5f911c30e
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS "setting" (
"user_id" integer NOT NULL,
"qr_background" varchar(7),
"qr_foreground" varchar(7)
);

@ -0,0 +1,256 @@
{
"id": "6fa088cf-5f05-40aa-bb38-94848c4feb92",
"prevId": "9a3733db-b0ef-4744-b0bf-f69e9b03917c",
"version": "5",
"dialect": "pg",
"tables": {
"project": {
"name": "project",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
"columns": {
"token": {
"name": "token",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"setting": {
"name": "setting",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"shortener": {
"name": "shortener",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"link": {
"name": "link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"project_id": {
"name": "project_id",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"visitor": {
"name": "visitor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"shortener_id": {
"name": "shortener_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"country_code": {
"name": "country_code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

@ -29,6 +29,13 @@
"when": 1701590526323, "when": 1701590526323,
"tag": "0003_glorious_norrin_radd", "tag": "0003_glorious_norrin_radd",
"breakpoints": true "breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1704723435338,
"tag": "0004_cynical_the_hand",
"breakpoints": true
} }
] ]
} }

@ -64,6 +64,8 @@ export const user = pgTable('user', {
.notNull(), .notNull(),
}) })
export type User = InferSelectModel<typeof user>
export const visitor = pgTable('visitor', { export const visitor = pgTable('visitor', {
id: serial('id').primaryKey().notNull(), id: serial('id').primaryKey().notNull(),
shortenerId: integer('shortener_id').notNull(), shortenerId: integer('shortener_id').notNull(),
@ -99,4 +101,15 @@ export const sessionRelations = relations(session, ({ one }) => ({
}), }),
})) }))
export type User = InferSelectModel<typeof user> export const setting = pgTable('setting', {
userId: integer('user_id').notNull(),
qr_background: varchar('qr_background', { length: 7 }),
qr_foreground: varchar('qr_foreground', { length: 7 }),
})
export const settingRelations = relations(setting, ({ one }) => ({
user: one(user, {
fields: [setting.userId],
references: [user.id],
}),
}))

@ -12,3 +12,8 @@ export const userCreateSchema = z.object({
password: z.string(), password: z.string(),
password_confirm: z.string(), password_confirm: z.string(),
}) })
export const qrUpdateSchema = z.object({
qr_background: z.string().max(7),
qr_foreground: z.string().max(7),
})

@ -43,5 +43,9 @@ export const load = (async (event) => {
where: (project, { eq }) => eq(project.userId, user.id), where: (project, { eq }) => eq(project.userId, user.id),
}) })
return { shorteners, projects, selected_project } const settings = await db.query.setting.findFirst({
where: (settings, { eq }) => eq(settings.userId, user.id),
})
return { shorteners, projects, selected_project, settings }
}) satisfies PageServerLoad }) satisfies PageServerLoad

@ -282,7 +282,10 @@
<Badge variant="secondary"> <Badge variant="secondary">
{data.shortener_url + '/' + qrCode} {data.shortener_url + '/' + qrCode}
</Badge> </Badge>
<Qr value={data.shortener_url + '/' + qrCode} /> <Qr
value={data.shortener_url + '/' + qrCode}
background={data.settings?.qr_background || '#fff'}
color={data.settings?.qr_foreground || '#000'} />
</div> </div>
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>

@ -3,14 +3,14 @@
import SidebarNav from './(components)/sidebar-nav.svelte' import SidebarNav from './(components)/sidebar-nav.svelte'
const sidebarNavItems = [ const sidebarNavItems = [
{
title: 'Profile',
href: '/settings',
},
{ {
title: 'Account', title: 'Account',
href: '/settings/account', href: '/settings/account',
}, },
{
title: 'QR',
href: '/settings/qr',
},
] ]
</script> </script>

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

@ -1,15 +0,0 @@
<script lang="ts">
import type { PageData } from './$types'
import { Separator } from '$lib/components/ui/separator'
export let data: PageData
</script>
<div class="space-y-6">
<div>
<h3 class="text-lg font-medium">Profile</h3>
<p class="text-muted-foreground text-sm">
This is how others will see you on the site.
</p>
</div>
<Separator />
</div>

@ -0,0 +1,37 @@
<script>
import { onMount } from 'svelte'
import { default as QrCode } from 'qrious'
export let errorCorrection = 'L'
export let background = '#fff'
export let color = '#000'
export let size = '300'
export let value = 'example.com/abcdefgh'
export let padding = 10
export let className = 'qrcode'
let image = ''
function generateQrCode() {
if (!document || !window) {
return
}
const QRcode = new QrCode()
QRcode.set({
background,
foreground: color,
level: errorCorrection,
padding,
size,
value,
})
image = QRcode.toDataURL()
}
onMount(() => {
generateQrCode()
})
</script>
<img src={image} alt={value} class={className} />

@ -0,0 +1,10 @@
import { db } from '$lib/db'
import type { PageServerLoad } from './$types'
export const load = (async (event) => {
const settings = await db.query.setting.findFirst({
where: (setting, { eq }) =>
eq(setting.userId, event.locals.userObject.id),
})
return { settings }
}) satisfies PageServerLoad

@ -0,0 +1,87 @@
<script lang="ts">
import { Separator } from '$lib/components/ui/separator'
import { Input } from '$lib/components/ui/input'
import { Label } from '$lib/components/ui/label'
import type { PageData } from './$types'
import Button from '$lib/components/ui/button/button.svelte'
import { Loader2 } from 'lucide-svelte'
import { invalidateAll } from '$app/navigation'
import { toast } from 'svelte-sonner'
import DemoQr from './(components)/DemoQR.svelte'
export let data: PageData
let isLoading = false
let qr_data = {
background_color: data.settings?.qr_background || '#fff',
foreground_color: data.settings?.qr_foreground || '#000',
}
console.log(qr_data)
const submit = async () => {
isLoading = true
try {
const response = await fetch('/api/account/qr', {
method: 'PUT',
body: JSON.stringify({
qr_background: qr_data.background_color,
qr_foreground: qr_data.foreground_color,
}),
})
const body = await response.json()
if (body.success) {
toast.success('Account Details Updated')
await invalidateAll()
}
} catch (error) {
toast.error('Error Occurred')
} finally {
isLoading = false
}
}
</script>
<div class="space-y-6">
<div>
<h3 class="text-lg font-medium">Account</h3>
<p class="text-muted-foreground text-sm">
Update your account settings.
</p>
</div>
<Separator />
{#key qr_data}
<DemoQr
background={qr_data.background_color}
color={qr_data.foreground_color} />
{/key}
<div class="flex w-full max-w-sm flex-col gap-2">
<Label for="background_color">Background Color</Label>
<Input
type="text"
id="background_color"
placeholder="#ffffff"
bind:value={qr_data.background_color} />
</div>
<div class="flex w-full max-w-sm flex-col gap-2">
<Label for="foreground_color">Foreground Color</Label>
<Input
type="text"
id="foreground_color"
placeholder="#000000"
bind:value={qr_data.foreground_color} />
</div>
<Button disabled={isLoading} on:click={submit} class="flex gap-2">
{#if isLoading}
<Loader2 class="animate-spin" />
{/if}
Save</Button>
</div>

@ -0,0 +1,32 @@
import { db } from '$lib/db'
import { setting } from '$lib/db/schema'
import { qrUpdateSchema } from '$lib/server/types'
import { eq } from 'drizzle-orm'
import type { RequestHandler } from './$types'
export const PUT: RequestHandler = async (event) => {
const body = await event.request.json()
const qrUpdate = qrUpdateSchema.safeParse(body)
const userId = event.locals.userObject.id
if (!qrUpdate.success) {
return new Response(JSON.stringify({ success: false }))
}
try {
const settings = await db.query.setting.findFirst({
where: (settingData, { eq }) => eq(settingData.userId, userId),
})
if (!settings) {
await db.insert(setting).values({ userId })
}
await db
.update(setting)
.set(qrUpdate.data)
.where(eq(setting.userId, userId))
return new Response(JSON.stringify({ success: true }))
} catch (error) {
return new Response(JSON.stringify({ success: false }))
}
}
Loading…
Cancel
Save