From 4d245ff5fbf6e025b06bdc70bb9a5bf4252b4930 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Sun, 1 Sep 2024 15:26:06 +0800 Subject: [PATCH] move settings directly into user table + added qr logo upload --- frontend/drizzle/0021_faulty_ironclad.sql | 4 + frontend/drizzle/0022_cynical_emma_frost.sql | 11 + .../drizzle/0023_dashing_radioactive_man.sql | 1 + frontend/drizzle/meta/0021_snapshot.json | 486 ++++++++++++++++++ frontend/drizzle/meta/0022_snapshot.json | 486 ++++++++++++++++++ frontend/drizzle/meta/0023_snapshot.json | 458 +++++++++++++++++ frontend/drizzle/meta/_journal.json | 21 + frontend/src/lib/db/schema.ts | 23 +- frontend/src/lib/db/types.ts | 3 +- .../(app)/dashboard/links/+page.server.ts | 5 - .../links/[id]/qr/(components)/qr.svelte | 10 +- .../dashboard/links/[id]/qr/+page.server.ts | 6 +- .../dashboard/links/[id]/qr/+page.svelte | 24 +- .../[id]/(components)/ShortenerCard.svelte | 3 +- .../dashboard/projects/[id]/+page.server.ts | 5 - .../dashboard/projects/[id]/+page.svelte | 3 +- .../links/[linkid]/qr/(components)/qr.svelte | 10 +- .../[id]/links/[linkid]/qr/+page.svelte | 10 +- .../[id]/settings/(components)/DemoQR.svelte | 16 +- .../[id]/settings/(components)/form.svelte | 200 +++---- .../projects/[id]/settings/+page.server.ts | 31 +- .../projects/[id]/settings/+page.svelte | 5 +- .../projects/[id]/settings/schema.ts | 11 + .../settings/qr/(components)/DemoQR.svelte | 18 +- .../dashboard/settings/qr/+page.server.ts | 62 ++- .../(app)/dashboard/settings/qr/+page.svelte | 205 ++++---- .../(app)/dashboard/settings/qr/schema.ts | 11 + .../(public)/url/[id]/qr/+page.server.ts | 23 +- .../routes/(public)/url/[id]/qr/+page.svelte | 19 +- 29 files changed, 1884 insertions(+), 286 deletions(-) create mode 100644 frontend/drizzle/0021_faulty_ironclad.sql create mode 100644 frontend/drizzle/0022_cynical_emma_frost.sql create mode 100644 frontend/drizzle/0023_dashing_radioactive_man.sql create mode 100644 frontend/drizzle/meta/0021_snapshot.json create mode 100644 frontend/drizzle/meta/0022_snapshot.json create mode 100644 frontend/drizzle/meta/0023_snapshot.json diff --git a/frontend/drizzle/0021_faulty_ironclad.sql b/frontend/drizzle/0021_faulty_ironclad.sql new file mode 100644 index 0000000..d7a3f48 --- /dev/null +++ b/frontend/drizzle/0021_faulty_ironclad.sql @@ -0,0 +1,4 @@ +ALTER TABLE "project" ADD COLUMN "qr_image_base64" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "qr_background" varchar(7) DEFAULT '#fff' NOT NULL;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "qr_foreground" varchar(7) DEFAULT '#000' NOT NULL;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "qr_image_base64" text; \ No newline at end of file diff --git a/frontend/drizzle/0022_cynical_emma_frost.sql b/frontend/drizzle/0022_cynical_emma_frost.sql new file mode 100644 index 0000000..a864b76 --- /dev/null +++ b/frontend/drizzle/0022_cynical_emma_frost.sql @@ -0,0 +1,11 @@ +-- Custom SQL migration file, put you code below! -- +-- copy qr background setting +UPDATE "user" +SET qr_background = setting.qr_background +FROM setting +WHERE setting.user_id = "user".id; +-- copy qr foreground setting +UPDATE "user" +SET qr_foreground = setting.qr_foreground +FROM setting +WHERE setting.user_id = "user".id; \ No newline at end of file diff --git a/frontend/drizzle/0023_dashing_radioactive_man.sql b/frontend/drizzle/0023_dashing_radioactive_man.sql new file mode 100644 index 0000000..7d2725e --- /dev/null +++ b/frontend/drizzle/0023_dashing_radioactive_man.sql @@ -0,0 +1 @@ +DROP TABLE "setting"; \ No newline at end of file diff --git a/frontend/drizzle/meta/0021_snapshot.json b/frontend/drizzle/meta/0021_snapshot.json new file mode 100644 index 0000000..5bc5e07 --- /dev/null +++ b/frontend/drizzle/meta/0021_snapshot.json @@ -0,0 +1,486 @@ +{ + "id": "cc527753-148c-4459-ab86-7a6c95be8151", + "prevId": "956fa062-8556-4889-b3bb-49539bb7256f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.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 + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.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": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.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_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#fff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000'" + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.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 + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/0022_snapshot.json b/frontend/drizzle/meta/0022_snapshot.json new file mode 100644 index 0000000..b30c78e --- /dev/null +++ b/frontend/drizzle/meta/0022_snapshot.json @@ -0,0 +1,486 @@ +{ + "id": "dbe99c19-b436-46dd-af9d-2d3f7f33c0bd", + "prevId": "cc527753-148c-4459-ab86-7a6c95be8151", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.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 + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.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": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "columns": [ + "code" + ], + "nullsNotDistinct": false + } + } + }, + "public.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_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#fff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000'" + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + } + }, + "public.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 + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/0023_snapshot.json b/frontend/drizzle/meta/0023_snapshot.json new file mode 100644 index 0000000..2354561 --- /dev/null +++ b/frontend/drizzle/meta/0023_snapshot.json @@ -0,0 +1,458 @@ +{ + "id": "74a897dc-352a-4a82-8966-5a47a6719602", + "prevId": "dbe99c19-b436-46dd-af9d-2d3f7f33c0bd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.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 + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.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_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#fff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000'" + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.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 + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/_journal.json b/frontend/drizzle/meta/_journal.json index 5e6788d..530ae2e 100644 --- a/frontend/drizzle/meta/_journal.json +++ b/frontend/drizzle/meta/_journal.json @@ -148,6 +148,27 @@ "when": 1725037845646, "tag": "0020_warm_santa_claus", "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1725167182706, + "tag": "0021_faulty_ironclad", + "breakpoints": true + }, + { + "idx": 22, + "version": "7", + "when": 1725167233901, + "tag": "0022_cynical_emma_frost", + "breakpoints": true + }, + { + "idx": 23, + "version": "7", + "when": 1725167580022, + "tag": "0023_dashing_radioactive_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/frontend/src/lib/db/schema.ts b/frontend/src/lib/db/schema.ts index 49528f1..76b1759 100644 --- a/frontend/src/lib/db/schema.ts +++ b/frontend/src/lib/db/schema.ts @@ -6,6 +6,7 @@ import { integer, uuid, boolean, + text, } from 'drizzle-orm/pg-core' import { relations } from 'drizzle-orm' @@ -25,6 +26,12 @@ export const user = pgTable('user', { .$type<'free' | 'pro' | 'owner'>() .default('free'), stripeCustomerId: varchar('stripe_customer_id', { length: 255 }), + qrBackground: varchar('qr_background', { length: 7 }) + .notNull() + .default('#fff'), + qrForeground: varchar('qr_foreground', { length: 7 }) + .notNull() + .default('#000'), qrCornerSquareStyle: varchar('qr_corner_square_style') .$type<'dot' | 'square' | 'extra-rounded'>() .notNull() @@ -33,6 +40,7 @@ export const user = pgTable('user', { .$type<'square' | 'rounded'>() .notNull() .default('square'), + qrImageBase64: text('qr_image_base64'), }) export const shortener = pgTable('shortener', { @@ -82,6 +90,7 @@ export const project = pgTable('project', { .$type<'square' | 'rounded'>() .notNull() .default('square'), + qrImageBase64: text('qr_image_base64'), }) export const visitor = pgTable('visitor', { @@ -114,12 +123,6 @@ export const session = pgTable('session', { }).notNull(), }) -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 emailVerificationToken = pgTable( 'email_verification_token', { @@ -136,7 +139,6 @@ export const emailVerificationToken = pgTable( // relations export const userRelations = relations(user, ({ one, many }) => ({ shortener: many(shortener), - setting: one(setting), })) export const shortenerRelations = relations( @@ -178,10 +180,3 @@ export const sessionRelations = relations(session, ({ one }) => ({ references: [user.id], }), })) - -export const settingRelations = relations(setting, ({ one }) => ({ - user: one(user, { - fields: [setting.userId], - references: [user.id], - }), -})) diff --git a/frontend/src/lib/db/types.ts b/frontend/src/lib/db/types.ts index 386ce1d..b07870c 100644 --- a/frontend/src/lib/db/types.ts +++ b/frontend/src/lib/db/types.ts @@ -1,7 +1,6 @@ import type { InferSelectModel } from 'drizzle-orm' -import { project, setting, shortener, user } from './schema' +import { project, shortener, user } from './schema' export type User = InferSelectModel export type Shortener = InferSelectModel export type Project = InferSelectModel -export type Setting = InferSelectModel diff --git a/frontend/src/routes/(app)/dashboard/links/+page.server.ts b/frontend/src/routes/(app)/dashboard/links/+page.server.ts index 5952558..56404e7 100644 --- a/frontend/src/routes/(app)/dashboard/links/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/links/+page.server.ts @@ -117,15 +117,10 @@ export const load = (async (event) => { where: (project, { eq }) => eq(project.userId, user.id), }) - const settings = db.query.setting.findFirst({ - where: (settings, { eq }) => eq(settings.userId, user.id), - }) - return { shorteners, projects: await projects, selected_project, - settings: await settings, page, perPage, search, diff --git a/frontend/src/routes/(app)/dashboard/links/[id]/qr/(components)/qr.svelte b/frontend/src/routes/(app)/dashboard/links/[id]/qr/(components)/qr.svelte index 2f93bf7..ca1464b 100644 --- a/frontend/src/routes/(app)/dashboard/links/[id]/qr/(components)/qr.svelte +++ b/frontend/src/routes/(app)/dashboard/links/[id]/qr/(components)/qr.svelte @@ -12,6 +12,7 @@ export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' = 'square' export let dotStyle: 'square' | 'rounded' = 'square' + export let existingQrImage: string | null = null let image = '' @@ -43,9 +44,9 @@ data: value, width: 300, height: 300, - margin: 10, + margin: 1, qrOptions: { - errorCorrectionLevel: 'L', + errorCorrectionLevel: 'M', typeNumber: 0, }, backgroundOptions: { @@ -58,6 +59,11 @@ cornersSquareOptions: { type: cornerSquareStyle, }, + image: existingQrImage || undefined, + imageOptions: { + imageSize: 0.7, + margin: 8, + }, }) const blob = await qrcodestyling.getRawData() if (!blob) return diff --git a/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.server.ts b/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.server.ts index a2a9375..898c3e8 100644 --- a/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.server.ts @@ -18,9 +18,5 @@ export const load = (async (event) => { redirect(300, `/dashboard/links`) } - const settings = await db.query.setting.findFirst({ - where: (settings, { eq }) => eq(settings.userId, user.id), - }) - - return { shortener, settings } + return { shortener } }) satisfies PageServerLoad diff --git a/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.svelte b/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.svelte index 0b84cd1..98da061 100644 --- a/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.svelte +++ b/frontend/src/routes/(app)/dashboard/links/[id]/qr/+page.svelte @@ -11,20 +11,28 @@
+ dotStyle={data.user.qrDotStyle} + existingQrImage={data.user.qrImageBase64} />
{:else} + dotStyle={data.user.qrDotStyle} + existingQrImage={data.user.qrImageBase64} /> {/if} diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte index 60280d9..25bba30 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte @@ -7,7 +7,7 @@ import * as Avatar from '$lib/components/ui/avatar' import { Badge } from '$lib/components/ui/badge' import { ScrollArea } from '$lib/components/ui/scroll-area' - import type { Shortener, Project, Setting } from '$lib/db/types' + import type { Shortener, Project } from '$lib/db/types' import { BarChart, EditIcon, @@ -27,7 +27,6 @@ visitorCount: number } export let shortener_url: string - export let settings: Setting | undefined export let selected_project: Project let deleteDialogOpen = false diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts index 3b2c84c..72116bc 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts @@ -88,14 +88,9 @@ export const load = (async (event) => { ), ) - const settings = db.query.setting.findFirst({ - where: (settings, { eq }) => eq(settings.userId, user.id), - }) - return { selectedProject, shorteners, - settings: await settings, page, perPage, search, diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.svelte index 0c6ed9e..c1f1023 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.svelte @@ -184,8 +184,7 @@ + shortener_url={data.shortener_url} /> {/each} diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/(components)/qr.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/(components)/qr.svelte index 2f93bf7..ca1464b 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/(components)/qr.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/(components)/qr.svelte @@ -12,6 +12,7 @@ export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' = 'square' export let dotStyle: 'square' | 'rounded' = 'square' + export let existingQrImage: string | null = null let image = '' @@ -43,9 +44,9 @@ data: value, width: 300, height: 300, - margin: 10, + margin: 1, qrOptions: { - errorCorrectionLevel: 'L', + errorCorrectionLevel: 'M', typeNumber: 0, }, backgroundOptions: { @@ -58,6 +59,11 @@ cornersSquareOptions: { type: cornerSquareStyle, }, + image: existingQrImage || undefined, + imageOptions: { + imageSize: 0.7, + margin: 8, + }, }) const blob = await qrcodestyling.getRawData() if (!blob) return diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/+page.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/+page.svelte index 7969c39..94a49a3 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/+page.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/links/[linkid]/qr/+page.svelte @@ -16,20 +16,22 @@
+ dotStyle={data.project.qrDotStyle} + existingQrImage={data.project.qrImageBase64} />
{:else} + dotStyle={data.project.qrDotStyle} + existingQrImage={data.project.qrImageBase64} /> {/if} diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/DemoQR.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/DemoQR.svelte index d37a6b1..7326bf0 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/DemoQR.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/DemoQR.svelte @@ -8,6 +8,8 @@ export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' = 'square' export let dotStyle: 'square' | 'rounded' = 'square' + export let existingQrImage: string | null = null + export let qrImage: File | null = null let image = '' @@ -17,15 +19,19 @@ } try { + const qrImageDataUrl = qrImage + ? URL.createObjectURL(qrImage) + : existingQrImage || undefined + const qrcodestyling = new ( await import('qr-code-styling') ).default({ data: value, width: 300, height: 300, - margin: 10, + margin: 1, qrOptions: { - errorCorrectionLevel: 'L', + errorCorrectionLevel: 'M', typeNumber: 0, }, backgroundOptions: { @@ -38,6 +44,11 @@ cornersSquareOptions: { type: cornerSquareStyle, }, + image: qrImageDataUrl, + imageOptions: { + imageSize: 0.7, + margin: 8, + }, }) const blob = await qrcodestyling.getRawData() if (!blob) return @@ -52,6 +63,7 @@ color && cornerSquareStyle && dotStyle && + (qrImage === null || qrImage) && generateQrCode() onMount(() => { diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/form.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/form.svelte index de72c3e..f3c0088 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/form.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/(components)/form.svelte @@ -15,6 +15,7 @@ import { cn } from '$lib/utils' export let data: SuperValidated> + export let qrImageBase64: string | null = null export let isPro: boolean = false const form = superForm(data, { @@ -37,6 +38,7 @@
@@ -52,7 +54,9 @@ background={$formData.qr_background} color={$formData.qr_foreground} cornerSquareStyle={$formData.qrCornerSquareStyle} - dotStyle={$formData.qrDotStyle} /> + dotStyle={$formData.qrDotStyle} + existingQrImage={qrImageBase64} + qrImage={$formData.qrImage} /> @@ -70,94 +74,112 @@ QR Code foreground color - {#if isPro} - - - - Corner Square Style (Pro) - - - -
- - - -
-
- - - - Dot Style (Pro) - - - -
- - -
-
- {/if} + + + + Image (Pro) + + + ($formData.qrImage = e.currentTarget.files?.item(0))} /> + + + + + + + Corner Square Style (Pro) + + + +
+ + + +
+
+ + + + Dot Style (Pro) + + + +
+ + +
+
{#if $submitting} diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.server.ts b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.server.ts index 21994be..0754d68 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.server.ts @@ -6,6 +6,7 @@ import { setMessage, superValidate, fail, + withFiles, } from 'sveltekit-superforms' import { formSchema, @@ -56,6 +57,7 @@ export const load = (async (event) => { }, zod(formSchema), ), + qrImageBase64: project.qrImageBase64, enableCustomDomainForm: await superValidate( { enableDomain: project.custom_domain || '' }, zod(enableCustomDomainFormSchema), @@ -81,12 +83,25 @@ export const actions: Actions = { update: async (event) => { const form = await superValidate(event, zod(formSchema)) if (!form.valid) { - return fail(400, { - form, - }) + return fail( + 400, + withFiles({ + form, + }), + ) } const user = event.locals.user + const qrImage = form.data.qrImage + + const qrImageType = qrImage ? qrImage.type : undefined + const qrImageBlob = qrImage + ? await qrImage.arrayBuffer() + : undefined + const qrImageBase64 = qrImageBlob + ? Buffer.from(qrImageBlob).toString('base64') + : undefined + await db .update(projectTable) .set({ @@ -99,6 +114,12 @@ export const actions: Actions = { : undefined, qrDotStyle: user.plan !== 'free' ? form.data.qrDotStyle : undefined, + qrImageBase64: + user.plan !== 'free' + ? qrImage + ? `data:${qrImageType};base64,${qrImageBase64}` + : undefined + : undefined, }) .where( and( @@ -107,9 +128,9 @@ export const actions: Actions = { ), ) - return { + return withFiles({ form, - } + }) }, enable_custom_domain: async (event) => { const form = await superValidate( diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.svelte index 7ae84f4..78460d2 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/+page.svelte @@ -309,7 +309,10 @@ - + diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/schema.ts b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/schema.ts index f1dcaaa..3fa1fde 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/settings/schema.ts +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/settings/schema.ts @@ -12,6 +12,17 @@ export const formSchema = z.object({ .max(7), qrCornerSquareStyle: z.custom<'dot' | 'square' | 'extra-rounded'>(), qrDotStyle: z.custom<'square' | 'rounded'>(), + qrImage: z + .instanceof(File, { message: 'Please upload a file' }) + .refine( + (file) => + file.type === 'image/jpeg' || file.type === 'image/png', + { + message: 'Only JPEG or PNG files are allowed', + }, + ) + .optional() + .nullable(), }) export const enableCustomDomainFormSchema = z.object({ diff --git a/frontend/src/routes/(app)/dashboard/settings/qr/(components)/DemoQR.svelte b/frontend/src/routes/(app)/dashboard/settings/qr/(components)/DemoQR.svelte index d37a6b1..9117529 100644 --- a/frontend/src/routes/(app)/dashboard/settings/qr/(components)/DemoQR.svelte +++ b/frontend/src/routes/(app)/dashboard/settings/qr/(components)/DemoQR.svelte @@ -4,10 +4,12 @@ export let background = '#fff' export let color = '#000' - export let value = 'example.com/abcdefgh' + export let value = 'abcdefghajskdadsj' export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' = 'square' export let dotStyle: 'square' | 'rounded' = 'square' + export let existingQrImage: string | null = null + export let qrImage: File | null = null let image = '' @@ -17,15 +19,19 @@ } try { + const qrImageDataUrl = qrImage + ? URL.createObjectURL(qrImage) + : existingQrImage || undefined + const qrcodestyling = new ( await import('qr-code-styling') ).default({ data: value, width: 300, height: 300, - margin: 10, + margin: 1, qrOptions: { - errorCorrectionLevel: 'L', + errorCorrectionLevel: 'M', typeNumber: 0, }, backgroundOptions: { @@ -38,6 +44,11 @@ cornersSquareOptions: { type: cornerSquareStyle, }, + image: qrImageDataUrl, + imageOptions: { + imageSize: 0.7, + margin: 8, + }, }) const blob = await qrcodestyling.getRawData() if (!blob) return @@ -52,6 +63,7 @@ color && cornerSquareStyle && dotStyle && + (qrImage === null || qrImage) && generateQrCode() onMount(() => { diff --git a/frontend/src/routes/(app)/dashboard/settings/qr/+page.server.ts b/frontend/src/routes/(app)/dashboard/settings/qr/+page.server.ts index 763dc0e..9af6a54 100644 --- a/frontend/src/routes/(app)/dashboard/settings/qr/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/settings/qr/+page.server.ts @@ -1,25 +1,20 @@ import { db } from '$lib/db' import type { PageServerLoad, Actions } from './$types' import { fail } from '@sveltejs/kit' -import { superValidate } from 'sveltekit-superforms' +import { superValidate, withFiles } from 'sveltekit-superforms' import { formSchema } from './schema' import { zod } from 'sveltekit-superforms/adapters' -import { setting } from '$lib/db/schema' import { eq } from 'drizzle-orm' import { user as userTable } from '$lib/db/schema' export const load = (async (event) => { const user = event.locals.user - const settings = await db.query.setting.findFirst({ - where: (setting, { eq }) => - eq(setting.userId, event.locals.user.id), - }) - const qr_background = settings?.qr_background || '#000' - const qr_foreground = settings?.qr_foreground || '#fff' + const qr_background = user.qrBackground + const qr_foreground = user.qrForeground return { - settings, + qrImageBase64: user.qrImageBase64, form: await superValidate( { qr_background, @@ -28,6 +23,7 @@ export const load = (async (event) => { qrDotStyle: user.qrDotStyle, }, zod(formSchema), + { errors: false }, ), } }) satisfies PageServerLoad @@ -36,40 +32,56 @@ export const actions: Actions = { default: async (event) => { const form = await superValidate(event, zod(formSchema)) if (!form.valid) { - return fail(400, { - form, - }) + return fail( + 400, + withFiles({ + form, + }), + ) } const user = event.locals.user const userId = event.locals.user.id - const settings = await db.query.setting.findFirst({ - where: (settingData, { eq }) => eq(settingData.userId, userId), - }) - - if (!settings) { - await db.insert(setting).values({ userId }) - } const { qr_background, qr_foreground, qrCornerSquareStyle, qrDotStyle, + qrImage, } = form.data + + const qrImageType = qrImage ? qrImage.type : undefined + const qrImageBlob = qrImage + ? await qrImage.arrayBuffer() + : undefined + const qrImageBase64 = qrImageBlob + ? Buffer.from(qrImageBlob).toString('base64') + : undefined + await db - .update(setting) - .set({ qr_background, qr_foreground }) - .where(eq(setting.userId, userId)) + .update(userTable) + .set({ + qrBackground: qr_background, + qrForeground: qr_foreground, + }) + .where(eq(userTable.id, userId)) if (user.plan !== 'free') { await db .update(userTable) - .set({ qrCornerSquareStyle, qrDotStyle }) + .set({ + qrCornerSquareStyle, + qrDotStyle, + qrImageBase64: qrImage + ? `data:${qrImageType};base64,${qrImageBase64}` + : undefined, + }) + .where(eq(userTable.id, userId)) } - return { + return withFiles({ form, - } + }) }, } diff --git a/frontend/src/routes/(app)/dashboard/settings/qr/+page.svelte b/frontend/src/routes/(app)/dashboard/settings/qr/+page.svelte index 44bfa45..0896997 100644 --- a/frontend/src/routes/(app)/dashboard/settings/qr/+page.svelte +++ b/frontend/src/routes/(app)/dashboard/settings/qr/+page.svelte @@ -45,10 +45,16 @@ background={$formData.qr_background} color={$formData.qr_foreground} cornerSquareStyle={$formData.qrCornerSquareStyle} - dotStyle={$formData.qrDotStyle} /> + dotStyle={$formData.qrDotStyle} + existingQrImage={data.qrImageBase64} + qrImage={$formData.qrImage} /> - + Background Color @@ -65,95 +71,112 @@ QR Code foreground color - {#if data.user.plan !== 'free'} - - - - Corner Square Style (Pro) - - - -
- - - -
-
- - - - Dot Style (Pro) - - - -
- - -
-
- {/if} + + + + Image (Pro) + + + ($formData.qrImage = e.currentTarget.files?.item(0))} /> + + + + + + + Corner Square Style (Pro) + + + +
+ + + +
+
+ + + + Dot Style (Pro) + + + +
+ + +
+
{#if $submitting} diff --git a/frontend/src/routes/(app)/dashboard/settings/qr/schema.ts b/frontend/src/routes/(app)/dashboard/settings/qr/schema.ts index 6b85eae..54c4427 100644 --- a/frontend/src/routes/(app)/dashboard/settings/qr/schema.ts +++ b/frontend/src/routes/(app)/dashboard/settings/qr/schema.ts @@ -11,4 +11,15 @@ export const formSchema = z.object({ .max(7), qrCornerSquareStyle: z.custom<'dot' | 'square' | 'extra-rounded'>(), qrDotStyle: z.custom<'square' | 'rounded'>(), + qrImage: z + .instanceof(File, { message: 'Please upload a file' }) + .refine( + (file) => + file.type === 'image/jpeg' || file.type === 'image/png', + { + message: 'Only JPEG or PNG files are allowed', + }, + ) + .optional() + .nullable(), }) diff --git a/frontend/src/routes/(public)/url/[id]/qr/+page.server.ts b/frontend/src/routes/(public)/url/[id]/qr/+page.server.ts index 4a1e51b..3d8ee5e 100644 --- a/frontend/src/routes/(public)/url/[id]/qr/+page.server.ts +++ b/frontend/src/routes/(public)/url/[id]/qr/+page.server.ts @@ -12,11 +12,7 @@ export const load = (async (event) => { const shortener = await db.query.shortener.findFirst({ where: (shortener, { eq }) => eq(shortener.code, id), with: { - user: { - with: { - setting: true, - }, - }, + user: true, project: true, }, }) @@ -25,29 +21,32 @@ export const load = (async (event) => { redirect(301, `/dashboard/links`) } - let colorSetting: { + let setting: { color: { background: string | null; foreground: string | null } cornerSquareType: 'square' | 'dot' | 'extra-rounded' dotStyle: 'square' | 'rounded' + image: string | null } | null = null if (color === 'true') { if (shortener.project) { - colorSetting = { + setting = { color: { background: shortener.project.qr_background, foreground: shortener.project.qr_foreground, }, cornerSquareType: shortener.project.qrCornerSquareStyle, dotStyle: shortener.project.qrDotStyle, + image: shortener.project.qrImageBase64, } - } else if (shortener.user.setting) { - colorSetting = { + } else if (shortener.user) { + setting = { color: { - background: shortener.user.setting.qr_background, - foreground: shortener.user.setting.qr_foreground, + background: shortener.user.qrBackground, + foreground: shortener.user.qrForeground, }, cornerSquareType: shortener.user.qrCornerSquareStyle, dotStyle: shortener.user.qrDotStyle, + image: shortener.user.qrImageBase64, } } } @@ -57,5 +56,5 @@ export const load = (async (event) => { ? shortener.project.custom_domain || shortenerUrl : shortenerUrl - return { shortener, url, colorSetting, shortenerId: id } + return { shortener, url, setting, shortenerId: id } }) satisfies PageServerLoad diff --git a/frontend/src/routes/(public)/url/[id]/qr/+page.svelte b/frontend/src/routes/(public)/url/[id]/qr/+page.svelte index b82db8d..485ee76 100644 --- a/frontend/src/routes/(public)/url/[id]/qr/+page.svelte +++ b/frontend/src/routes/(public)/url/[id]/qr/+page.svelte @@ -34,23 +34,28 @@ const qrcodestyling = new ( await import('qr-code-styling') ).default({ - data: data.url + '/' + data.shortenerId, + data: 'https://' + data.url + '/' + data.shortenerId, width: 300, height: 300, - margin: 10, + margin: 1, qrOptions: { - errorCorrectionLevel: 'L', + errorCorrectionLevel: 'M', typeNumber: 0, }, backgroundOptions: { - color: data.colorSetting?.color.background || '#fff', + color: data.setting?.color.background || '#fff', }, dotsOptions: { - color: data.colorSetting?.color.foreground || '#000', - type: data.colorSetting?.dotStyle || 'square', + color: data.setting?.color.foreground || '#000', + type: data.setting?.dotStyle || 'square', }, cornersSquareOptions: { - type: data.colorSetting?.cornerSquareType || 'square', + type: data.setting?.cornerSquareType || 'square', + }, + image: data.setting?.image || undefined, + imageOptions: { + imageSize: 0.7, + margin: 8, }, }) const blob = await qrcodestyling.getRawData()