diff --git a/bun.lock b/bun.lock index 40631d4..3a253e3 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "@auth/drizzle-adapter": "^1.9.0", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.12", + "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", @@ -20,8 +21,7 @@ "d3-scale": "^4.0.2", "drizzle-orm": "^0.43.1", "framer-motion": "^12.6.3", - "html2canvas": "^1.4.1", - "html2canvas-pro": "^1.5.10", + "geist": "^1.4.2", "lucide-react": "^0.487.0", "next": "15.2.4", "next-auth": "^5.0.0-beta.26", @@ -38,6 +38,7 @@ "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", "ua-parser-js": "^2.0.3", + "zustand": "^5.0.4", }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -255,6 +256,8 @@ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw=="], + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.4", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.7", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.4", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.4", "@radix-ui/react-portal": "1.1.6", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-roving-focus": "1.1.7", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+qYq6LfbiGo97Zz9fioX83HCiIYYFNs8zAsVCMQrIakoNYylIzWuoD/anAD3UzvvR6cnswmfRFJFq/zYYq/k7Q=="], "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="], @@ -789,6 +792,8 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "geist": ["geist@1.4.2", "", { "peerDependencies": { "next": ">=13.2.0" } }, "sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], @@ -1285,6 +1290,8 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zustand": ["zustand@5.0.4", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -1305,6 +1312,8 @@ "@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "@radix-ui/react-menu/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg=="], @@ -1453,6 +1462,8 @@ "@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + "@radix-ui/react-menu/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], "@radix-ui/react-menu/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], @@ -1513,6 +1524,8 @@ "react-simple-maps/d3-zoom/d3-transition": ["d3-transition@2.0.0", "", { "dependencies": { "d3-color": "1 - 2", "d3-dispatch": "1 - 2", "d3-ease": "1 - 2", "d3-interpolate": "1 - 2", "d3-timer": "1 - 2" }, "peerDependencies": { "d3-selection": "2" } }, "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog=="], + "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + "@radix-ui/react-menu/@radix-ui/react-roving-focus/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], "react-simple-maps/d3-geo/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], diff --git a/drizzle.config.ts b/drizzle.config.ts index 6660d25..6d386b9 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,9 +1,10 @@ import { defineConfig } from "drizzle-kit"; + export default defineConfig({ dialect: "postgresql", schema: "./src/lib/schema.ts", out: "./drizzle", - dbCredentials:{ - url: process.env.POSTGRESS_URL! - } -}); \ No newline at end of file + dbCredentials: { + url: process.env.POSTGRESS_URL!, + }, +}); diff --git a/drizzle/0002_curved_nomad.sql b/drizzle/0002_curved_nomad.sql new file mode 100644 index 0000000..97898b4 --- /dev/null +++ b/drizzle/0002_curved_nomad.sql @@ -0,0 +1 @@ +ALTER TABLE "project" ADD COLUMN "icon" text; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..359e1e1 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1337 @@ +{ + "id": "c7230629-59ae-43af-a202-57e607d60ca3", + "prevId": "0a803f54-3ca9-4bcc-946a-cc5d7b896134", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics": { + "name": "analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "totalPageVisits": { + "name": "totalPageVisits", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "totalVisitors": { + "name": "totalVisitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "analytics_projectId_project_id_fk": { + "name": "analytics_projectId_project_id_fk", + "tableFrom": "analytics", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "analytics_projectId_unique": { + "name": "analytics_projectId_unique", + "nullsNotDistinct": false, + "columns": [ + "projectId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authenticator": { + "name": "authenticator", + "schema": "", + "columns": { + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "authenticator_userId_user_id_fk": { + "name": "authenticator_userId_user_id_fk", + "tableFrom": "authenticator", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "authenticator_userId_credentialID_pk": { + "name": "authenticator_userId_credentialID_pk", + "columns": [ + "userId", + "credentialID" + ] + } + }, + "uniqueConstraints": { + "authenticator_credentialID_unique": { + "name": "authenticator_credentialID_unique", + "nullsNotDistinct": false, + "columns": [ + "credentialID" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bugReport": { + "name": "bugReport", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'inReview'" + }, + "ownerId": { + "name": "ownerId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bugReport_ownerId_user_id_fk": { + "name": "bugReport_ownerId_user_id_fk", + "tableFrom": "bugReport", + "tableTo": "user", + "columnsFrom": [ + "ownerId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.countryAnalytics": { + "name": "countryAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "countryCode": { + "name": "countryCode", + "type": "varchar(2)", + "primaryKey": false, + "notNull": true + }, + "countryName": { + "name": "countryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "country_analytics_idx": { + "name": "country_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_country": { + "name": "unique_analytics_country", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "countryCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "countryAnalytics_analyticsId_analytics_id_fk": { + "name": "countryAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "countryAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deviceAnalytics": { + "name": "deviceAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deviceType": { + "name": "deviceType", + "type": "DeviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "device_analytics_idx": { + "name": "device_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_device": { + "name": "unique_analytics_device", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deviceType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deviceAnalytics_analyticsId_analytics_id_fk": { + "name": "deviceAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "deviceAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.osAnalytics": { + "name": "osAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "osName": { + "name": "osName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "os_analytics_idx": { + "name": "os_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_os": { + "name": "unique_analytics_os", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "osName", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "osAnalytics_analyticsId_analytics_id_fk": { + "name": "osAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "osAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.performanceAnalytics": { + "name": "performanceAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "loadTime": { + "name": "loadTime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "domReady": { + "name": "domReady", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "networkLatency": { + "name": "networkLatency", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processingTime": { + "name": "processingTime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "totalTime": { + "name": "totalTime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "performance_analytics_idx": { + "name": "performance_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "performance_date_idx": { + "name": "performance_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "performanceAnalytics_analyticsId_analytics_id_fk": { + "name": "performanceAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "performanceAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ownerId": { + "name": "ownerId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "project_owner_idx": { + "name": "project_owner_idx", + "columns": [ + { + "expression": "ownerId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_ownerId_user_id_fk": { + "name": "project_ownerId_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": [ + "ownerId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_domain_unique": { + "name": "project_domain_unique", + "nullsNotDistinct": false, + "columns": [ + "domain" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routeAnalytics": { + "name": "routeAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "route": { + "name": "route", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "pageVisits": { + "name": "pageVisits", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "route_analytics_idx": { + "name": "route_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_route": { + "name": "unique_analytics_route", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "route", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routeAnalytics_analyticsId_analytics_id_fk": { + "name": "routeAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "routeAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sourceAnalytics": { + "name": "sourceAnalytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sourceName": { + "name": "sourceName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "source_analytics_idx": { + "name": "source_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_source": { + "name": "unique_analytics_source", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sourceName", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sourceAnalytics_analyticsId_analytics_id_fk": { + "name": "sourceAnalytics_analyticsId_analytics_id_fk", + "tableFrom": "sourceAnalytics", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'USER'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.visitData": { + "name": "visitData", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "analyticsId": { + "name": "analyticsId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "pageVisits": { + "name": "pageVisits", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "visitors": { + "name": "visitors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "visit_data_analytics_idx": { + "name": "visit_data_analytics_idx", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "visit_data_date_idx": { + "name": "visit_data_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_analytics_date": { + "name": "unique_analytics_date", + "columns": [ + { + "expression": "analyticsId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "visitData_analyticsId_analytics_id_fk": { + "name": "visitData_analyticsId_analytics_id_fk", + "tableFrom": "visitData", + "tableTo": "analytics", + "columnsFrom": [ + "analyticsId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.DeviceType": { + "name": "DeviceType", + "schema": "public", + "values": [ + "DESKTOP", + "MOBILE", + "TABLET" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 1d3620a..6db290f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1745764604516, "tag": "0001_illegal_devos", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1747472843948, + "tag": "0002_curved_nomad", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 8be98f3..764f45b 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,19 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", - "db:generate": "drizzle-kit generate --dialect=postgresql --schema=src/lib/schema.ts --out=./drizzle" + "db:generate": "drizzle-kit generate --dialect=postgresql --schema=src/lib/schema.ts --out=./drizzle", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio" }, "dependencies": { "@auth/drizzle-adapter": "^1.9.0", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.12", + "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", @@ -26,8 +29,7 @@ "d3-scale": "^4.0.2", "drizzle-orm": "^0.43.1", "framer-motion": "^12.6.3", - "html2canvas": "^1.4.1", - "html2canvas-pro": "^1.5.10", + "geist": "^1.4.2", "lucide-react": "^0.487.0", "next": "15.2.4", "next-auth": "^5.0.0-beta.26", @@ -43,7 +45,8 @@ "sonner": "^2.0.3", "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", - "ua-parser-js": "^2.0.3" + "ua-parser-js": "^2.0.3", + "zustand": "^5.0.4" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/tracking-script.js b/public/tracking-script.js index 0c84878..09eab43 100644 --- a/public/tracking-script.js +++ b/public/tracking-script.js @@ -1,373 +1,376 @@ (function () { - "use strict"; - var location = window.location; - var document = window.document; - var scriptElement = document.currentScript; - var dataDomain = scriptElement.getAttribute("data-domain"); - - // Enhanced UTM parameter tracking - let queryString = location.search; - const params = new URLSearchParams(queryString); - var utmParams = { - source: params.get("utm_source"), - medium: params.get("utm_medium"), - campaign: params.get("utm_campaign"), - term: params.get("utm_term"), - content: params.get("utm_content"), - }; - - var endpoint = "https://webtracker.avikmukherjee.tech/api/track"; - var sessionDuration = 30 * 60 * 1000; // 30 minutes in milliseconds - - // Visitor identification (anonymous) - function getVisitorId() { - var visitorId = localStorage.getItem("visitor_id"); - if (!visitorId) { - visitorId = "visitor-" + Math.random().toString(36).substr(2, 16); - localStorage.setItem("visitor_id", visitorId); - } - return visitorId; + "use strict"; + var location = window.location; + var document = window.document; + var scriptElement = document.currentScript; + var dataDomain = scriptElement.getAttribute("data-domain"); + + // Enhanced UTM parameter tracking + let queryString = location.search; + const params = new URLSearchParams(queryString); + var utmParams = { + source: params.get("utm_source"), + medium: params.get("utm_medium"), + campaign: params.get("utm_campaign"), + term: params.get("utm_term"), + content: params.get("utm_content"), + }; + + var endpoint = "https://webtracker.avikmukherjee.tech/api/track"; + var sessionDuration = 30 * 60 * 1000; // 30 minutes in milliseconds + + // Visitor identification (anonymous) + function getVisitorId() { + var visitorId = localStorage.getItem("visitor_id"); + if (!visitorId) { + visitorId = "visitor-" + Math.random().toString(36).substr(2, 16); + localStorage.setItem("visitor_id", visitorId); } - - function generateSessionId() { - return "session-" + Math.random().toString(36).substr(2, 9); - } - - function initializeSession() { - var sessionId = sessionStorage.getItem("session_id"); - var expirationTimestamp = sessionStorage.getItem( + return visitorId; + } + + function generateSessionId() { + return "session-" + Math.random().toString(36).substr(2, 9); + } + + function initializeSession() { + var sessionId = sessionStorage.getItem("session_id"); + var expirationTimestamp = sessionStorage.getItem( + "session_expiration_timestamp" + ); + var isNewSession = false; + + if ( + !sessionId || + !expirationTimestamp || + isSessionExpired(parseInt(expirationTimestamp)) + ) { + // Generate a new session ID + sessionId = generateSessionId(); + // Set the expiration timestamp + expirationTimestamp = Date.now() + sessionDuration; + // Store the session ID and expiration timestamp in sessionStorage + sessionStorage.setItem("session_id", sessionId); + sessionStorage.setItem( "session_expiration_timestamp", + expirationTimestamp + ); + isNewSession = true; + } else { + // Extend the session + expirationTimestamp = Date.now() + sessionDuration; + sessionStorage.setItem( + "session_expiration_timestamp", + expirationTimestamp ); - var isNewSession = false; - - if ( - !sessionId || - !expirationTimestamp || - isSessionExpired(parseInt(expirationTimestamp)) - ) { - // Generate a new session ID - sessionId = generateSessionId(); - // Set the expiration timestamp - expirationTimestamp = Date.now() + sessionDuration; - // Store the session ID and expiration timestamp in sessionStorage - sessionStorage.setItem("session_id", sessionId); - sessionStorage.setItem( - "session_expiration_timestamp", - expirationTimestamp, - ); - isNewSession = true; - } else { - // Extend the session - expirationTimestamp = Date.now() + sessionDuration; - sessionStorage.setItem( - "session_expiration_timestamp", - expirationTimestamp, - ); - } - - if (isNewSession) { - trackSessionStart(); - } - - return { - sessionId: sessionId, - expirationTimestamp: parseInt(expirationTimestamp), - isNewSession: isNewSession, - }; - } - - function isSessionExpired(expirationTimestamp) { - return Date.now() >= expirationTimestamp; } - - function resetActivityTimer() { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - var session = initializeSession(); - // Just updating the expiration time + + if (isNewSession) { + trackSessionStart(); } - - // User activity monitoring - ["mousedown", "keydown", "touchstart", "scroll"].forEach(function (evt) { - document.addEventListener(evt, throttle(resetActivityTimer, 5000), { - passive: true, - }); + + return { + sessionId: sessionId, + expirationTimestamp: parseInt(expirationTimestamp), + isNewSession: isNewSession, + }; + } + + function isSessionExpired(expirationTimestamp) { + return Date.now() >= expirationTimestamp; + } + + function resetActivityTimer() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + var session = initializeSession(); + // Just updating the expiration time + } + + // User activity monitoring + ["mousedown", "keydown", "touchstart", "scroll"].forEach(function (evt) { + document.addEventListener(evt, throttle(resetActivityTimer, 5000), { + passive: true, }); - - // Throttle function to limit how often a function is called - function throttle(func, limit) { - var lastCall = 0; - return function () { - var now = Date.now(); - if (now - lastCall >= limit) { - lastCall = now; - func.apply(this, arguments); - } - }; - } - - // Function to send tracking events to the endpoint - function trigger(eventName, eventData, options) { - var session = initializeSession(); - var visitorId = getVisitorId(); - var source = ""; - if (document.referrer) { - try { - const referrerUrl = new URL(document.referrer); - source = referrerUrl.hostname; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - // Invalid referrer URL - source = "unknown"; - } - } else { - source = "direct"; + }); + + // Throttle function to limit how often a function is called + function throttle(func, limit) { + var lastCall = 0; + return function () { + var now = Date.now(); + if (now - lastCall >= limit) { + lastCall = now; + func.apply(this, arguments); } - - var payload = { - event: eventName, - url: location.href, - path: location.pathname, - domain: dataDomain, - referrer: document.referrer, - title: document.title, - utm: utmParams, - source: source, - visitor_id: visitorId, - session_id: session.sessionId, - timestamp: new Date().toISOString(), - screen: { - width: window.innerWidth, - height: window.innerHeight, - }, - language: navigator.language, - user_agent: navigator.userAgent, - data: eventData || {}, - }; - - // Using sendBeacon API for more reliable data sending, falling back to XHR - if (navigator.sendBeacon && !options?.forceXHR) { - navigator.sendBeacon(endpoint, JSON.stringify(payload)); - options?.callback?.(); - } else { - sendRequest(payload, options); + }; + } + + // Function to send tracking events to the endpoint + function trigger(eventName, eventData, options) { + var session = initializeSession(); + var visitorId = getVisitorId(); + var source = ""; + if (document.referrer) { + try { + const referrerUrl = new URL(document.referrer); + source = referrerUrl.hostname; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + // Invalid referrer URL + source = "unknown"; } + } else { + source = "direct"; } - - // Function to send HTTP requests - function sendRequest(payload, options) { - var request = new XMLHttpRequest(); - request.open("POST", endpoint, true); - request.setRequestHeader("Content-Type", "application/json"); - request.onreadystatechange = function () { - if (request.readyState === 4) { - options?.callback?.(); - } - }; - request.send(JSON.stringify(payload)); - } - - // Queue of tracking events - var queue = (window.your_tracking && window.your_tracking.q) || []; - - // Enhanced API with more options - window.your_tracking = function (eventName, eventData, options) { - trigger(eventName, eventData, options); - }; - - // Public API methods - window.your_tracking.pageview = function (customData) { - trigger("pageview", customData); - }; - - window.your_tracking.event = function (category, action, label, value) { - trigger("event", { category, action, label, value }); - }; - - window.your_tracking.timing = function (category, variable, time, label) { - trigger("timing", { category, variable, time, label }); + + var payload = { + event: eventName, + url: location.href, + path: location.pathname, + domain: dataDomain, + referrer: document.referrer, + title: document.title, + utm: utmParams, + source: source, + visitor_id: visitorId, + session_id: session.sessionId, + timestamp: new Date().toISOString(), + screen: { + width: window.innerWidth, + height: window.innerHeight, + }, + language: navigator.language, + user_agent: navigator.userAgent, + data: eventData || {}, }; - - // Process queued events - for (var i = 0; i < queue.length; i++) { - var args = queue[i]; - if (typeof args[0] === "string") { - window.your_tracking.apply(this, args); - } + + // Using sendBeacon API for more reliable data sending, falling back to XHR + if (navigator.sendBeacon && !options?.forceXHR) { + navigator.sendBeacon(endpoint, JSON.stringify(payload)); + options?.callback?.(); + } else { + sendRequest(payload, options); } - - function trackPageView() { - window.your_tracking.pageview(); + } + + // Function to send HTTP requests + function sendRequest(payload, options) { + var request = new XMLHttpRequest(); + request.open("POST", endpoint, true); + request.setRequestHeader("Content-Type", "application/json"); + request.onreadystatechange = function () { + if (request.readyState === 4) { + options?.callback?.(); + } + }; + request.send(JSON.stringify(payload)); + } + + // Queue of tracking events + var queue = (window.your_tracking && window.your_tracking.q) || []; + + // Enhanced API with more options + window.your_tracking = function (eventName, eventData, options) { + trigger(eventName, eventData, options); + }; + + // Public API methods + window.your_tracking.pageview = function (customData) { + trigger("pageview", customData); + }; + + window.your_tracking.event = function (category, action, label, value) { + trigger("event", { category, action, label, value }); + }; + + window.your_tracking.timing = function (category, variable, time, label) { + trigger("timing", { category, variable, time, label }); + }; + + // Process queued events + for (var i = 0; i < queue.length; i++) { + var args = queue[i]; + if (typeof args[0] === "string") { + window.your_tracking.apply(this, args); } - - function trackSessionStart() { - var referrerInfo = document.referrer - ? { - url: document.referrer, - domain: new URL(document.referrer).hostname, - } - : null; - - trigger("session_start", { - landing_page: location.href, - referrer: referrerInfo, - }); + } + + function trackPageView() { + window.your_tracking.pageview(); + } + + function trackSessionStart() { + var referrerInfo = document.referrer + ? { + url: document.referrer, + domain: new URL(document.referrer).hostname, + } + : null; + + trigger("session_start", { + landing_page: location.href, + referrer: referrerInfo, + }); + } + + function trackSessionEnd() { + var session = sessionStorage.getItem("session_id"); + if (session) { + trigger( + "session_end", + { + duration: + Date.now() - + parseInt(sessionStorage.getItem("last_activity") || Date.now()), + }, + { forceXHR: true } + ); } - - function trackSessionEnd() { - var session = sessionStorage.getItem("session_id"); - if (session) { - trigger( - "session_end", - { - duration: - Date.now() - - parseInt(sessionStorage.getItem("last_activity") || Date.now()), - }, - { forceXHR: true }, - ); - } + } + + // Track performance metrics + function trackPerformance() { + if (window.performance && window.performance.timing) { + var timing = window.performance.timing; + var performanceData = { + load_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), + dom_ready: Math.max( + 0, + timing.domContentLoadedEventEnd - timing.navigationStart + ), + network_latency: Math.max(0, timing.responseEnd - timing.requestStart), + processing_time: Math.max(0, timing.domComplete - timing.responseEnd), + total_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), + }; + + // Wait for the load event to finish + setTimeout(function () { + trigger("performance", performanceData); + }, 0); } - - // Track performance metrics - function trackPerformance() { - if (window.performance && window.performance.timing) { - var timing = window.performance.timing; - var performanceData = { - load_time: Math.max(0,timing.loadEventEnd - timing.navigationStart), - dom_ready: Math.max(0, timing.domContentLoadedEventEnd - timing.navigationStart), - network_latency: Math.max(0, timing.responseEnd - timing.requestStart), - processing_time: Math.max(0, timing.domComplete - timing.responseEnd), - total_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), - }; - - // Wait for the load event to finish - setTimeout(function () { - trigger("performance", performanceData); - }, 0); - } + } + + // Track outbound links + document.addEventListener("click", function (event) { + var target = event.target.closest("a"); + if (!target) return; + + var href = target.getAttribute("href") || ""; + var isOutbound = + href.indexOf("http") === 0 && !href.includes(location.hostname); + var isDownload = /\.(pdf|zip|docx?|xlsx?|pptx?|rar|tar|gz|exe)$/i.test( + href + ); + + if (isOutbound) { + trigger("outbound_click", { + url: href, + text: target.innerText, + target: target.getAttribute("target"), + }); } - - // Track outbound links - document.addEventListener("click", function (event) { - var target = event.target.closest("a"); - if (!target) return; - - var href = target.getAttribute("href") || ""; - var isOutbound = - href.indexOf("http") === 0 && !href.includes(location.hostname); - var isDownload = /\.(pdf|zip|docx?|xlsx?|pptx?|rar|tar|gz|exe)$/i.test( - href, - ); - - if (isOutbound) { - trigger("outbound_click", { - url: href, - text: target.innerText, - target: target.getAttribute("target"), - }); - } - - if (isDownload) { - trigger("download", { - file: href, - name: href.split("/").pop(), - }); - } - }); - - // Track form submissions - document.addEventListener("submit", function (event) { - var form = event.target; - if (!form || !form.tagName || form.tagName.toLowerCase() !== "form") return; - - var formId = form.id || form.getAttribute("name") || "unknown_form"; - trigger("form_submit", { - form_id: formId, - form_class: form.className, - form_action: form.action, + + if (isDownload) { + trigger("download", { + file: href, + name: href.split("/").pop(), }); + } + }); + + // Track form submissions + document.addEventListener("submit", function (event) { + var form = event.target; + if (!form || !form.tagName || form.tagName.toLowerCase() !== "form") return; + + var formId = form.id || form.getAttribute("name") || "unknown_form"; + trigger("form_submit", { + form_id: formId, + form_class: form.className, + form_action: form.action, }); - - // SPA navigation tracking - var initialPathname = location.pathname; - var lastHistoryState = history.state; - - // Listen for history API changes - var originalPushState = history.pushState; - history.pushState = function () { - originalPushState.apply(this, arguments); - handleUrlChange(); - }; - - var originalReplaceState = history.replaceState; - history.replaceState = function () { - originalReplaceState.apply(this, arguments); - handleUrlChange(); - }; - - function handleUrlChange() { - if ( - location.pathname !== initialPathname || - history.state !== lastHistoryState - ) { - setTimeout(function () { - trackPageView(); - initialPathname = location.pathname; - lastHistoryState = history.state; - }, 50); - } + }); + + // SPA navigation tracking + var initialPathname = location.pathname; + var lastHistoryState = history.state; + + // Listen for history API changes + var originalPushState = history.pushState; + history.pushState = function () { + originalPushState.apply(this, arguments); + handleUrlChange(); + }; + + var originalReplaceState = history.replaceState; + history.replaceState = function () { + originalReplaceState.apply(this, arguments); + handleUrlChange(); + }; + + function handleUrlChange() { + if ( + location.pathname !== initialPathname || + history.state !== lastHistoryState + ) { + setTimeout(function () { + trackPageView(); + initialPathname = location.pathname; + lastHistoryState = history.state; + }, 50); } - - // Track scrolling depth - var scrollDepthMarkers = [25, 50, 75, 90]; - var scrollDepthTracked = {}; - - window.addEventListener( - "scroll", - throttle(function () { - var scrollTop = window.pageYOffset || document.documentElement.scrollTop; - var scrollHeight = - document.documentElement.scrollHeight - - document.documentElement.clientHeight; - var scrollPercent = Math.round((scrollTop / scrollHeight) * 100); - - scrollDepthMarkers.forEach(function (marker) { - if (scrollPercent >= marker && !scrollDepthTracked[marker]) { - scrollDepthTracked[marker] = true; - trigger("scroll_depth", { depth: marker }); - } - }); - }, 1000), - ); - - // Initialize everything - function initialize() { - // Check for existing session - initializeSession(); - - // Track performance - if (document.readyState === "complete") { - trackPerformance(); - } else { - window.addEventListener("load", trackPerformance); - } - - // Track initial page view - trackPageView(); - - // Clean up when the page is unloaded - window.addEventListener("beforeunload", function () { - // Update last activity time to calculate session duration - sessionStorage.setItem("last_activity", Date.now().toString()); - // Use a synchronous request for beforeunload - trackSessionEnd(); + } + + // Track scrolling depth + var scrollDepthMarkers = [25, 50, 75, 90]; + var scrollDepthTracked = {}; + + window.addEventListener( + "scroll", + throttle(function () { + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + var scrollHeight = + document.documentElement.scrollHeight - + document.documentElement.clientHeight; + var scrollPercent = Math.round((scrollTop / scrollHeight) * 100); + + scrollDepthMarkers.forEach(function (marker) { + if (scrollPercent >= marker && !scrollDepthTracked[marker]) { + scrollDepthTracked[marker] = true; + trigger("scroll_depth", { depth: marker }); + } }); - - // Event listeners for navigation - window.addEventListener("popstate", handleUrlChange); - window.addEventListener("hashchange", handleUrlChange); + }, 1000) + ); + + // Initialize everything + function initialize() { + // Check for existing session + initializeSession(); + + // Track performance + if (document.readyState === "complete") { + trackPerformance(); + } else { + window.addEventListener("load", trackPerformance); } - - // Start tracking - initialize(); - })(); \ No newline at end of file + + // Track initial page view + trackPageView(); + + // Clean up when the page is unloaded + window.addEventListener("beforeunload", function () { + // Update last activity time to calculate session duration + sessionStorage.setItem("last_activity", Date.now().toString()); + // Use a synchronous request for beforeunload + trackSessionEnd(); + }); + + // Event listeners for navigation + window.addEventListener("popstate", handleUrlChange); + window.addEventListener("hashchange", handleUrlChange); + } + + // Start tracking + initialize(); +})(); diff --git a/src/app/api/project/route.ts b/src/app/api/project/route.ts index ba97764..20943bc 100644 --- a/src/app/api/project/route.ts +++ b/src/app/api/project/route.ts @@ -1,70 +1,164 @@ import { auth } from "@/auth"; import { db } from "@/lib/db"; +import { projects } from "@/lib/schema"; +import { load } from "cheerio"; import { revalidatePath, revalidateTag } from "next/cache"; import { NextResponse } from "next/server"; -import { projects } from "@/lib/schema"; +import { Agent, fetch } from "undici"; + +export async function POST(req: Request) { + try { + const session = await auth(); + if (!session) { + return NextResponse.json( + { + user: null, + message: "Unauthorized", + success: false, + }, + { status: 403 } + ); + } + + if (!session?.user?.id) { + return NextResponse.json( + { + user: null, + message: "Unauthorized", + success: false, + }, + { status: 403 } + ); + } + const values = await req.json(); + + if (!values.domain) { + return NextResponse.json( + { message: "Missing required fields", success: false }, + { status: 400 } + ); + } + + const validDomain = values.domain.trim().toLowerCase(); + + if (!validDomain.includes(".")) { + return NextResponse.json( + { message: "Invalid domain", success: false }, + { status: 400 } + ); + } + + const domainMetadata = await getDomainMetadata(validDomain); + + const exisitingProject = await db.query.projects.findFirst({ + where: (projects, { eq }) => eq(projects.domain, validDomain), + }); + + if (exisitingProject) { + return NextResponse.json( + { message: "Domain already exists", success: false }, + { status: 400 } + ); + } -export async function POST(req: Request){ - try{ - const session = await auth(); - if(!session){ - return NextResponse.json({ - user: null, - message: "Unauthorized", - success: false - }, - {status: 403} - ) - } - - if(!session?.user?.id){ - return NextResponse.json({ - user: null, - message: "Unauthorized", - success: false - }, - {status: 403} - ) - } - - const values = await req.json(); - if (!values.domain || !values.name || !values.description) { - return NextResponse.json( - { message: "Missing required fields", success: false }, - { status: 400 } - ); - } - - const exisitingProject = await db.query.projects.findFirst({ - where: (projects, { eq }) => eq(projects.domain, values.domain), - }) - - if (exisitingProject) { - return NextResponse.json( - { message: "Domain already exists", success: false }, - { status: 400 } - ); - } - - const newProject = await db.insert(projects).values({ - domain: values.domain, - name: values.name, - description: values.description, - ownerId: session.user.id, - }) - - revalidatePath("/projects"); - revalidateTag("projects"); - - return NextResponse.json({ - newProject, message: "Project created successfully", success: true - }, { status: 201 }); - - }catch (error) { - console.log("Error creating project", error); - return NextResponse.json( - { message: "Internal server error", success: false }, - { status: 500 } - ); + const newProject = await db.insert(projects).values({ + domain: values.domain, + name: domainMetadata.title || values.domain, + description: domainMetadata.description || "", + icon: domainMetadata.iconLink || "", + ownerId: session.user.id, + }); + + revalidatePath("/projects"); + revalidateTag("projects"); + + return NextResponse.json( + { + newProject, + message: "Project created successfully", + success: true, + }, + { status: 201 } + ); + } catch (error) { + console.log("Error creating project", error); + return NextResponse.json( + { message: "Internal server error", success: false }, + { status: 500 } + ); + } +} + +/** + * Fetches and extracts metadata from a domain + * @param domain - The domain to fetch metadata from (e.g., 'example.com') + * @returns An object containing the title, description, and icon link + */ +interface DomainMetadata { + title: string | null; + description: string | null; + iconLink: string | null; +} + +async function getDomainMetadata(domain: string): Promise { + try { + const url = domain.startsWith("http") ? domain : `https://${domain}`; + + // Create a custom agent that ignores SSL certificate errors + const undiciAgent = new Agent({ + connect: { + rejectUnauthorized: false, + }, + }); + + const response = await fetch(url, { + dispatcher: + process.env.NODE_ENV === "development" ? undiciAgent : undefined, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch ${url}. Status: ${response.status}`); } -} \ No newline at end of file + + const html = await response.text(); + const $ = load(html); + + const title = + $("title").text() || + $('meta[property="og:title"]').attr("content") || + null; + + const description = + $('meta[name="description"]').attr("content") || + $('meta[property="og:description"]').attr("content") || + null; + + let iconLink = + $('link[rel="icon"]').attr("href") || + $('link[rel="shortcut icon"]').attr("href") || + $('link[rel="apple-touch-icon"]').attr("href") || + "/favicon.ico"; + + if (iconLink && !iconLink.startsWith("http")) { + const baseUrl = new URL(url); + iconLink = iconLink.startsWith("/") + ? `${baseUrl.protocol}//${baseUrl.host}${iconLink}` + : `${baseUrl.protocol}//${baseUrl.host}/${iconLink}`; + } + + console.log({ title, description, iconLink }); + + return { + title, + description, + iconLink, + }; + } catch (error) { + console.error(`Error fetching metadata for ${domain}:`, error); + return { + title: null, + description: null, + iconLink: null, + }; + } +} diff --git a/src/app/dashboard/[websiteName]/page.tsx b/src/app/dashboard/[websiteName]/page.tsx index cb1dcfd..cd585e3 100644 --- a/src/app/dashboard/[websiteName]/page.tsx +++ b/src/app/dashboard/[websiteName]/page.tsx @@ -11,43 +11,41 @@ import TopPagesChart from "@/components/dashboard/analytics/topPages"; import PerformanceMetricsCard from "@/components/dashboard/analytics/Performance"; import CountryDistribution from "@/components/dashboard/analytics/countryDistribution"; import OperatingSystemsChart from "@/components/dashboard/analytics/osDistribution"; -import Snippet from "@/components/dashboard/snippet"; -import AnalyticsSummaryDownload from "@/components/dashboard/analytics/analytics-download"; import { AnalyticsData } from "@/lib/types"; import { Metadata } from "next"; import { fetchLinkPreview } from "@/app/actions/actions"; import WebsitePreview from "@/components/dashboard/analytics/websitePreview"; +import NoPageViews from "@/components/dashboard/no-page-view"; export const metadata: Metadata = { title: { default: "Dashboard", template: "%s | WebTracker", }, - description: "A web analytics tool for tracking user behavior and performance - Dashboard", - + description: + "A web analytics tool for tracking user behavior and performance - Dashboard", }; - export default async function DashboardPage({ params, }: { params: Promise<{ websiteName: string }>; }) { const data = await params; - + // Fetch data server-side const analyticsResponse = await getAnalytics(data.websiteName); const analyticsData = analyticsResponse?.response as AnalyticsData | null; const responseStatus = analyticsResponse?.status; // Check if data exists - const hasNoData = !analyticsData?.analytics || ( - !analyticsData.analytics.countryAnalytics?.length && - !analyticsData.analytics.deviceAnalytics?.length && - !analyticsData.analytics.osAnalytics?.length && - !analyticsData.analytics.sourceAnalytics?.length && - !analyticsData.analytics.routeAnalytics?.length - ); + const hasNoData = + !analyticsData?.analytics || + (!analyticsData.analytics.countryAnalytics?.length && + !analyticsData.analytics.deviceAnalytics?.length && + !analyticsData.analytics.osAnalytics?.length && + !analyticsData.analytics.sourceAnalytics?.length && + !analyticsData.analytics.routeAnalytics?.length); const websiteMetadata = await fetchLinkPreview(data.websiteName); @@ -60,10 +58,15 @@ export default async function DashboardPage({ -

Access Denied

+

+ Access Denied +

You don't have permission to view analytics for{" "} - {data.websiteName}. + + {data.websiteName} + + .

-
+ {/*

Analytics for {data.websiteName} @@ -98,16 +99,10 @@ export default async function DashboardPage({

View detailed analytics and insights for your website

-

+
*/} {hasNoData ? ( -
- -
+ ) : ( <>
@@ -129,14 +124,15 @@ export default async function DashboardPage({
- +
- - )}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 8746335..c44a12c 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,77 +1,71 @@ import { auth } from "@/auth"; -import AddWebsite from "@/components/dashboard/add-website"; +import AddNewSite from "@/components/dashboard/add-new-site"; import { WebsiteCard } from "@/components/dashboard/wesbite-card"; -import { Button } from "@/components/ui/button"; -import {redirect} from "next/navigation"; -import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Plus } from "lucide-react"; -import { getAllProjects } from "../actions/actions"; import { Metadata } from "next"; +import { redirect } from "next/navigation"; +import { getAllProjects } from "../actions/actions"; export const metadata: Metadata = { title: { default: "Dashboard", template: "%s | WebTracker", }, - description: "A web analytics tool for tracking user behavior and performance - Dashboard", + description: + "A web analytics tool for tracking user behavior and performance - Dashboard", }; +export default async function Dashboard() { + const session = await auth(); + if (!session) { + redirect("/"); + } -export default async function Dashboard(){ - const session = await auth(); - if(!session){ - redirect("/"); - } + if (!session?.user?.id) { + redirect("/"); + } - if(!session?.user?.id){ - redirect("/"); - } + const projects = await getAllProjects(session.user.id); - const projects = await getAllProjects(session.user.id); - - - return( + return (
-
-

Your Websites

- - - - - - Add a new website - - - -
{projects?.length === 0 ? ( -
-

No websites added yet.

+
+

+ No websites added yet. +

+ +
) : ( -
- {projects?.map((website) => ( - - ))} -
+ <> +
+

+ Your Websites +

+ +
+
+ {projects?.map((website) => ( + + ))} +
+ )}
- ) -} \ No newline at end of file + ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 2ce8cbc..fb0e05f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -44,7 +44,7 @@ } :root { - --radius: 0.75rem; + --radius: 0.25rem; /* Background & Foreground */ --background: oklch(1 0 0); @@ -61,8 +61,8 @@ --primary-foreground: oklch(1 0 0); /* Secondary Colors */ - --secondary: oklch(0.980 0.008 275.087); - --secondary-foreground: oklch(0.235 0.010 275.254); + --secondary: oklch(0.98 0.008 275.087); + --secondary-foreground: oklch(0.235 0.01 275.254); /* Muted */ --muted: oklch(0.969 0.007 276.287); @@ -70,7 +70,7 @@ /* Accent */ --accent: oklch(0.969 0.007 276.287); - --accent-foreground: oklch(0.235 0.010 275.254); + --accent-foreground: oklch(0.235 0.01 275.254); /* Status Colors */ --destructive: oklch(0.656 0.195 27.234); @@ -84,17 +84,17 @@ /* Chart Colors */ --chart-1: oklch(0.569 0.215 251.258); --chart-2: oklch(0.642 0.158 159.271); - --chart-3: oklch(0.550 0.193 292.394); + --chart-3: oklch(0.55 0.193 292.394); --chart-4: oklch(0.756 0.187 81.572); - --chart-5: oklch(0.480 0.022 275.448); + --chart-5: oklch(0.48 0.022 275.448); /* Sidebar */ - --sidebar: oklch(0.980 0.008 275.087); + --sidebar: oklch(0.98 0.008 275.087); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.569 0.215 251.258); --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.969 0.007 276.287); - --sidebar-accent-foreground: oklch(0.235 0.010 275.254); + --sidebar-accent-foreground: oklch(0.235 0.01 275.254); --sidebar-border: oklch(0.906 0.012 285.573); --sidebar-ring: oklch(0.569 0.215 251.258); } @@ -102,21 +102,21 @@ .dark { /* Background & Foreground */ --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.980 0.008 275.087); + --foreground: oklch(0.98 0.008 275.087); /* Card & Popover */ - --card: oklch(0.235 0.010 275.254); - --card-foreground: oklch(0.980 0.008 275.087); - --popover: oklch(0.235 0.010 275.254); - --popover-foreground: oklch(0.980 0.008 275.087); + --card: oklch(0.235 0.01 275.254); + --card-foreground: oklch(0.98 0.008 275.087); + --popover: oklch(0.235 0.01 275.254); + --popover-foreground: oklch(0.98 0.008 275.087); /* Primary Colors */ --primary: oklch(0.553 0.226 269.114); --primary-foreground: oklch(1 0 0); /* Secondary Colors */ - --secondary: oklch(0.235 0.010 275.254); - --secondary-foreground: oklch(0.980 0.008 275.087); + --secondary: oklch(0.235 0.01 275.254); + --secondary-foreground: oklch(0.98 0.008 275.087); /* Muted */ --muted: oklch(0.329 0.013 276.198); @@ -124,10 +124,10 @@ /* Accent */ --accent: oklch(0.329 0.013 276.198); - --accent-foreground: oklch(0.980 0.008 275.087); + --accent-foreground: oklch(0.98 0.008 275.087); /* Status Colors */ - --destructive: oklch(0.711 0.180 21.251); + --destructive: oklch(0.711 0.18 21.251); --destructive-foreground: oklch(1 0 0); /* Borders & Inputs */ @@ -143,12 +143,12 @@ --chart-5: oklch(0.652 0.013 276.198); /* Sidebar */ - --sidebar: oklch(0.235 0.010 275.254); - --sidebar-foreground: oklch(0.980 0.008 275.087); + --sidebar: oklch(0.235 0.01 275.254); + --sidebar-foreground: oklch(0.98 0.008 275.087); --sidebar-primary: oklch(0.553 0.226 269.114); --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.329 0.013 276.198); - --sidebar-accent-foreground: oklch(0.980 0.008 275.087); + --sidebar-accent-foreground: oklch(0.98 0.008 275.087); --sidebar-border: oklch(0.329 0.013 276.198); --sidebar-ring: oklch(0.553 0.226 269.114); } @@ -161,7 +161,12 @@ @apply bg-background text-foreground font-sans; } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { @apply font-medium; } @@ -189,19 +194,38 @@ /* Essential animations */ @keyframes float { - 0%, 100% { transform: translateY(0px); } - 50% { transform: translateY(-10px); } + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } } @keyframes pulse { - 0%, 100% { transform: scale(1); opacity: 1; } - 50% { transform: scale(1.05); opacity: 0.8; } + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.05); + opacity: 0.8; + } } @keyframes blob { - 0%, 100% { transform: translate(0px, 0px) scale(1); } - 33% { transform: translate(30px, -50px) scale(1.1); } - 66% { transform: translate(-20px, 20px) scale(0.9); } + 0%, + 100% { + transform: translate(0px, 0px) scale(1); + } + 33% { + transform: translate(30px, -50px) scale(1.1); + } + 66% { + transform: translate(-20px, 20px) scale(0.9); + } } .animate-float { @@ -223,3 +247,20 @@ .animation-delay-4000 { animation-delay: 4s; } + +pre code { + @apply font-mono!; +} + +code.language-html, +code.language-jsx { + @apply bg-transparent!; +} + +pre { + @apply bg-[rgb(40,44,52)]!; +} + +.dark pre { + @apply bg-sidebar!; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f50fcbd..75f9520 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,74 +1,79 @@ -import Footer from '@/components/Footer/mainFooter' -import Header from '@/components/Header' -import { ThemeProvider } from '@/components/theme-provider' -import { Toaster } from '@/components/ui/sonner' -import type { Metadata } from 'next' -import { SessionProvider } from 'next-auth/react' -import { Plus_Jakarta_Sans } from 'next/font/google' -import './globals.css' -import type { PropsWithChildren } from 'react' +import Footer from "@/components/Footer/mainFooter"; +import Header from "@/components/Header"; +import { ThemeProvider } from "@/components/theme-provider"; +import { Toaster } from "@/components/ui/sonner"; +import type { Metadata } from "next"; +import { SessionProvider } from "next-auth/react"; +import { Plus_Jakarta_Sans } from "next/font/google"; +import "./globals.css"; +import type { PropsWithChildren } from "react"; +import { GeistMono } from "geist/font/mono"; const plusJakartaSans = Plus_Jakarta_Sans({ - subsets: [ 'latin' ], - variable: '--font-plus-jakarta', - weight: [ '300', '400', '500', '600', '700', '800' ] -}) + subsets: ["latin"], + variable: "--font-plus-jakarta", + weight: ["300", "400", "500", "600", "700", "800"], +}); export const metadata: Metadata = { title: { - default: 'WebTracker', - template: '%s | WebTracker' + default: "WebTracker", + template: "%s | WebTracker", }, - description: 'A web analytics tool for tracking user behavior and performance', + description: + "A web analytics tool for tracking user behavior and performance", keywords: [ - 'web analytics', - 'user behavior', - 'performance tracking', - 'data visualization', - 'real-time analytics', - 'user engagement', - 'website performance', - 'analytics dashboard', - 'data analysis', - 'user experience', - 'conversion tracking', - 'event tracking' ], + "web analytics", + "user behavior", + "performance tracking", + "data visualization", + "real-time analytics", + "user engagement", + "website performance", + "analytics dashboard", + "data analysis", + "user experience", + "conversion tracking", + "event tracking", + ], openGraph: { - description: 'A web analytics tool for tracking user behavior and performance', - title: 'WebTracker', - type: 'website', - siteName: 'WebTracker', - locale: 'en_US', - url: 'https://webtracker.avikmukherjee.tech', + description: + "A web analytics tool for tracking user behavior and performance", + title: "WebTracker", + type: "website", + siteName: "WebTracker", + locale: "en_US", + url: "https://webtracker.avikmukherjee.tech", images: [ { - url: 'https://webtracker.avikmukherjee.tech/og-image.png', + url: "https://webtracker.avikmukherjee.tech/og-image.png", width: 1200, height: 630, - alt: 'WebTracker' - } - ] + alt: "WebTracker", + }, + ], }, twitter: { - card: 'summary_large_image', - description: 'A web analytics tool for tracking user behavior and performance', - title: 'WebTracker', + card: "summary_large_image", + description: + "A web analytics tool for tracking user behavior and performance", + title: "WebTracker", images: [ { - url: 'https://webtracker.avikmukherjee.tech/og-image.png', + url: "https://webtracker.avikmukherjee.tech/og-image.png", width: 1200, height: 630, - alt: 'WebTracker' - } - ] - } -} + alt: "WebTracker", + }, + ], + }, +}; export default function RootLayout({ children }: PropsWithChildren) { return ( -
+
{children}