diff --git a/.env.example b/.env.example index 2a6e557a..d3cfae53 100644 --- a/.env.example +++ b/.env.example @@ -30,4 +30,7 @@ GOOGLE_APPLICATION_CREDENTIALS_JSON="" # The content of the JSON file you d # Upstash_Redis UPSTASH_REDIS_REST_URL="" # REST URL of your Upstash Redis database -UPSTASH_REDIS_REST_TOKEN="" # REST API token for Upstash Redis \ No newline at end of file +UPSTASH_REDIS_REST_TOKEN="" # REST API token for Upstash Redis + +NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN="" +NEXT_PUBLIC_POSTHOG_HOST="https://e.papers.codechefvit.com" diff --git a/package.json b/package.json index ff09c541..1c869576 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,12 @@ "next-themes": "^0.3.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "4.8.69", + "posthog-js": "^1.419.4", + "posthog-node": "^5.51.2", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", "raw-loader": "^4.0.2", - "react": "link:@embedpdf/plugin-viewport/react", + "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1020bbec..47701830 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,28 +22,28 @@ importers: version: 3.2.2(react@18.3.1) '@embedpdf/core': specifier: ^2.14.0 - version: 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/engines': specifier: ^2.14.0 - version: 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-document-manager': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-export': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-render': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-scroll': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-viewport': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/plugin-zoom': specifier: ^2.14.0 - version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + version: 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@google-cloud/storage': specifier: ^7.17.1 version: 7.17.1 @@ -161,6 +161,12 @@ importers: pdfjs-dist: specifier: 4.8.69 version: 4.8.69 + posthog-js: + specifier: ^1.419.4 + version: 1.419.4(@types/react@18.3.25)(react@18.3.1) + posthog-node: + specifier: ^5.51.2 + version: 5.51.2 prettier: specifier: ^3.5.3 version: 3.6.2 @@ -171,7 +177,7 @@ importers: specifier: ^4.0.2 version: 4.0.2(webpack@5.102.0) react: - specifier: link:@embedpdf/plugin-viewport/react + specifier: ^18.3.1 version: 18.3.1 react-beautiful-dnd: specifier: ^13.1.1 @@ -580,6 +586,15 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@posthog/browser-common@0.6.0': + resolution: {integrity: sha512-d6yBE7VeoU3JTpaab3CaCoDCseh0Ytx7sTe0v2ZxhtHNxoKk7rdqr92+bUz9F1T2CuJO5/OTkes4HWT2VHNrTA==} + + '@posthog/core@1.48.11': + resolution: {integrity: sha512-fvKbxGaUM8RuCDB1jdhSqAHYjk42INfJDWT1KVezn74sCrfnr1YaUafxzmcS+87D5FzbA2tu7IT0GQRV2WkkRg==} + + '@posthog/types@1.406.1': + resolution: {integrity: sha512-RhA00AvWrcEW3wbYgvIPr4EeUTu1BDHJx8eajwJ1moOYIchQtgkQcqgnUjwXz6jL1a8XJAIaZ9w7SZfYo1b/Rw==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1797,6 +1812,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + core-js@3.50.0: + resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1915,6 +1933,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dompurify@3.4.14: + resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2193,6 +2214,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.4.9: + resolution: {integrity: sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3126,8 +3150,33 @@ packages: resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} engines: {node: ^10 || ^12 || >=14} - preact@10.29.1: - resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + posthog-js@1.419.4: + resolution: {integrity: sha512-SpEgcWtHZ54HxtWl5jCxjHiG9C6t90iQVIra1T+U3veN9Q8vNx3yYsUuEV+dNF6g4M7HeV8Y1ZpNlTalWI8EYw==} + peerDependencies: + '@types/react': '>=16.8.0' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + posthog-node@5.51.2: + resolution: {integrity: sha512-K+aQnqyHEtx2O5YmjxByh3WnXXMsrAisW0hNSXwejM4v7cngrbqoA6rZevMSyaXNqx9xhBedTxz0orb1Xwxyew==} + engines: {node: ^20.20.0 || >=22.22.0} + peerDependencies: + rxjs: ^7.0.0 + peerDependenciesMeta: + rxjs: + optional: true + + preact@10.29.8: + resolution: {integrity: sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==} + peerDependencies: + preact-render-to-string: '>=5' + peerDependenciesMeta: + preact-render-to-string: + optional: true prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} @@ -3232,6 +3281,9 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3853,6 +3905,12 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-vitals@5.3.0: + resolution: {integrity: sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==} + + web-vitals@6.0.0: + resolution: {integrity: sha512-Guaibvy/+uNtL6Bsu4jmMJGzuSl91oeRH5iO9pPRbYftnFUr3yqT1TUNX/OE4o9HexuEMU3Kb/Wg7iKhlffZUA==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -4007,17 +4065,17 @@ snapshots: react: 18.3.1 tslib: 2.8.1 - '@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/engines': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/engines': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - '@embedpdf/engines@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/engines@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: '@embedpdf/fonts-arabic': 1.0.0 '@embedpdf/fonts-hebrew': 1.0.0 @@ -4028,7 +4086,7 @@ snapshots: '@embedpdf/fonts-tc': 1.0.0 '@embedpdf/models': 2.14.0 '@embedpdf/pdfium': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 @@ -4052,64 +4110,64 @@ snapshots: '@embedpdf/pdfium@2.14.0': {} - '@embedpdf/plugin-document-manager@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/plugin-document-manager@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - '@embedpdf/plugin-export@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/plugin-export@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - '@embedpdf/plugin-render@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/plugin-render@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - '@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - '@embedpdf/plugin-viewport': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) - preact: 10.29.1 + '@embedpdf/plugin-viewport': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - '@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': + '@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))': dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - preact: 10.29.1 + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 vue: 3.5.32(typescript@5.9.3) - ? '@embedpdf/plugin-zoom@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))' + ? '@embedpdf/plugin-zoom@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-scroll@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3))' : dependencies: - '@embedpdf/core': 2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/core': 2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) '@embedpdf/models': 2.14.0 - '@embedpdf/plugin-scroll': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) - '@embedpdf/plugin-viewport': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) - preact: 10.29.1 + '@embedpdf/plugin-scroll': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(@embedpdf/plugin-viewport@2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + '@embedpdf/plugin-viewport': 2.14.0(@embedpdf/core@2.14.0(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)))(preact@10.29.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(svelte@5.55.2)(vue@3.5.32(typescript@5.9.3)) + preact: 10.29.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) svelte: 5.55.2 @@ -4325,6 +4383,17 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@posthog/browser-common@0.6.0': + dependencies: + '@posthog/core': 1.48.11 + '@posthog/types': 1.406.1 + + '@posthog/core@1.48.11': + dependencies: + '@posthog/types': 1.406.1 + + '@posthog/types@1.406.1': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.0.1': @@ -5606,6 +5675,8 @@ snapshots: concat-map@0.0.1: {} + core-js@3.50.0: {} + core-util-is@1.0.3: {} cross-spawn@7.0.6: @@ -5702,6 +5773,10 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.4.14: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6118,6 +6193,8 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fflate@0.4.9: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -7080,7 +7157,29 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.29.1: {} + posthog-js@1.419.4(@types/react@18.3.25)(react@18.3.1): + dependencies: + '@posthog/browser-common': 0.6.0 + '@posthog/core': 1.48.11 + '@posthog/types': 1.406.1 + core-js: 3.50.0 + dompurify: 3.4.14 + fflate: 0.4.9 + preact: 10.29.8 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.3.0 + web-vitals-soft-navs: web-vitals@6.0.0 + optionalDependencies: + '@types/react': 18.3.25 + react: 18.3.1 + transitivePeerDependencies: + - preact-render-to-string + + posthog-node@5.51.2: + dependencies: + '@posthog/core': 1.48.11 + + preact@10.29.8: {} prebuild-install@7.1.3: dependencies: @@ -7136,6 +7235,8 @@ snapshots: dependencies: side-channel: 1.1.0 + query-selector-shadow-dom@1.0.1: {} + queue-microtask@1.2.3: {} raf-schd@4.0.3: {} @@ -7886,6 +7987,10 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-vitals@5.3.0: {} + + web-vitals@6.0.0: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index a0fbf2c6..15a6327b 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -1,6 +1,8 @@ import { success, failure } from "@/lib/utils/response"; -import { createPaperRequest } from "@/lib/services/paper" -import type { CreatePaperInputType } from "@/lib/services/paper" +import { createPaperRequest } from "@/lib/services/paper"; +import type { CreatePaperInputType } from "@/lib/services/paper"; +import { getPostHogClient } from "@/lib/posthog-server"; +import { randomUUID } from "crypto"; export async function POST(req: Request) { try { @@ -11,6 +13,21 @@ export async function POST(req: Request) { } const newRequest = await createPaperRequest({subject, exam, slot, year}); + + const posthog = getPostHogClient(); + if (posthog) { + posthog.capture({ + distinctId: randomUUID(), + event: "paper_request_created", + properties: { + exam, + slot, + year, + }, + }); + await posthog.flush(); + } + return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201); } catch (error) { console.error("Error creating paper request:", error); diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 471b5cb0..782a48ca 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,5 +1,7 @@ import { success, failure } from "@/lib/utils/response"; import { uploadPaper } from "@/lib/services/upload"; +import { getPostHogClient } from "@/lib/posthog-server"; +import { randomUUID } from "crypto"; export const runtime = "nodejs"; @@ -21,6 +23,20 @@ export async function POST(req: Request) { return failure(result.message, result.status); } + const posthog = getPostHogClient(); + if (posthog) { + posthog.capture({ + distinctId: randomUUID(), + event: "paper_upload_completed", + properties: { + file_count: files.length, + is_pdf: isPdf, + campus: campus ?? "unknown", + }, + }); + await posthog.flush(); + } + return success( { file_url: result.file_url, thumbnail_url: result.thumbnail_url }, "Created", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a62c9e8e..175ea07b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,6 +8,7 @@ import Navbar from "@/components/Navbar"; import Footer from "@/components/Footer"; import ChildrenWrapper from "@/components/ChildrenWrapper"; import { CoursesProvider } from "@/context/courseContext"; +import { PostHogProvider } from "@/components/PostHogProvider"; export const metadata: Metadata = { metadataBase: new URL("https://papers.codechefvit.com/"), @@ -113,21 +114,23 @@ export default function RootLayout({ - - -
- - - {children} -
-
+ + + +
+ + + {children} +
+
+
); diff --git a/src/components/Card.tsx b/src/components/Card.tsx index ff0646d6..4ceecb9f 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -28,6 +28,17 @@ interface CardProps { const Card = ({ paper, onSelect, isSelected, isShow=true }: CardProps) => { const [previewOpen, setPreviewOpen] = React.useState(false); + const paperContextValue = React.useMemo( + () => ({ + paperId: paper._id, + subject: paper.subject, + exam: paper.exam, + slot: paper.slot, + year: paper.year, + }), + [paper._id, paper.subject, paper.exam, paper.slot, paper.year], + ); + React.useEffect(() => { if (!previewOpen) return; @@ -60,8 +71,8 @@ const Card = ({ paper, onSelect, isSelected, isShow=true }: CardProps) => { window.open(paperLink, "_blank"); }} className={cn( - "cursor-pointer overflow-hidden rounded-sm border-2 border-[#734DFF] bg-[#FFFFFF] font-play transition-all duration-150 hover:bg-[#EFEAFF] dark:border-[#36266D] dark:bg-[#171720] hover:dark:bg-[#262635]", - isSelected && "ring-2 ring-[#7480FF] bg-[#EFEAFF]" + "cursor-pointer overflow-hidden rounded-sm border-2 border-[#734DFF] bg-[#FFFFFF] font-play shadow-sm transition-all duration-150 hover:-translate-y-0.5 hover:bg-[#EFEAFF] hover:shadow-md dark:border-[#36266D] dark:bg-[#171720] dark:shadow-none hover:dark:bg-[#262635]", + isSelected && "-translate-y-0.5 bg-[#EFEAFF] shadow-md ring-2 ring-[#7480FF]" )} > { {paper.slot} {paper.year} {paper.semester} + {paper.school && {paper.school}} @@ -147,15 +159,7 @@ const Card = ({ paper, onSelect, isSelected, isShow=true }: CardProps) => {
- +
{relatedSubjects.length > 0 && ( diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 945b0944..fbd72773 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -15,6 +15,7 @@ import { import { Mail } from "lucide-react"; import toast from "react-hot-toast"; import type { ApiResponse } from '@/interface' +import posthog from "posthog-js"; export default function Footer() { const [email, setEmail] = useState(""); @@ -42,6 +43,8 @@ export default function Footer() { }, ); + posthog.capture("newsletter_subscribed"); + setEmail(""); }; @@ -93,8 +96,6 @@ export default function Footer() {

Events

DevSOC - CookOff - Clueminati
{/* Projects */}
diff --git a/src/components/PostHogProvider.tsx b/src/components/PostHogProvider.tsx new file mode 100644 index 00000000..3d8989bf --- /dev/null +++ b/src/components/PostHogProvider.tsx @@ -0,0 +1,29 @@ +"use client"; + +import posthog from "posthog-js"; +import { PostHogProvider as PHProvider } from "posthog-js/react"; +import { useEffect } from "react"; + +export function PostHogProvider({ children }: { children: React.ReactNode }) { + useEffect(() => { + const token = process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN; + const host = process.env.NEXT_PUBLIC_POSTHOG_HOST; + if (!token || !host) { + if (process.env.NODE_ENV !== "production") { + console.error( + "NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and NEXT_PUBLIC_POSTHOG_HOST variables required by PostHog are missing or un-configured, this causes events to be silently missed. This error stops appearing once both variables are configured", + ); + } + return; + } + posthog.init(token, { + api_host: host, + ui_host: "https://us.posthog.com", + defaults: "2026-01-30", + capture_exceptions: true, + debug: process.env.NODE_ENV === "development", + }); + }, []); + + return {children}; +} diff --git a/src/components/ReportTagModal.tsx b/src/components/ReportTagModal.tsx index f056f498..b0360cc2 100644 --- a/src/components/ReportTagModal.tsx +++ b/src/components/ReportTagModal.tsx @@ -15,7 +15,8 @@ import { MultiSelect } from "@/components/multi-select"; import LabeledInput from "@/components/ui/LabeledInput"; import LabeledSelect from "@/components/ui/LabeledSelect"; import axios from "axios"; -import toast from "react-hot-toast"; +import toast from "react-hot-toast"; +import posthog from "posthog-js"; import { type ApiResponse } from '@/interface' type ReportResponse = ApiResponse<{ error?: string; message?: string }>; @@ -236,6 +237,11 @@ if (reportedFields.length === 0 && comment.trim().length === 0) { } ) .then(() => { + posthog.capture("paper_reported", { + fields_reported: reportedFields.map((f) => f.field), + fields_reported_count: reportedFields.length, + has_comment: comment.trim().length > 0, + }); modalSetOpen(false); setComment(""); setEmail(""); diff --git a/src/components/SelectionToolbar.tsx b/src/components/SelectionToolbar.tsx new file mode 100644 index 00000000..8803bb78 --- /dev/null +++ b/src/components/SelectionToolbar.tsx @@ -0,0 +1,89 @@ +"use client"; + +import React from "react"; +import { CheckSquare, Square, Download, Loader2 } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface SelectionToolbarProps { + selectedCount: number; + totalCount: number; + onSelectAll: () => void; + onDeselectAll: () => void; + onDownload: () => void; + isDownloading: boolean; +} + +const SelectionToolbar: React.FC = ({ + selectedCount, + totalCount, + onSelectAll, + onDeselectAll, + onDownload, + isDownloading, +}) => { + const allSelected = totalCount > 0 && selectedCount === totalCount; + const hasSelection = selectedCount > 0; + + return ( +
+ + {selectedCount} / {totalCount} selected + + +
+ + + + +
+ ); +}; + +export default SelectionToolbar; diff --git a/src/components/ShareButton.tsx b/src/components/ShareButton.tsx index d9387b77..9490b0b7 100644 --- a/src/components/ShareButton.tsx +++ b/src/components/ShareButton.tsx @@ -15,6 +15,7 @@ import { FaShare } from "react-icons/fa"; import QR from "./qr"; import { Button } from "./ui/button"; import { usePathname } from "next/navigation"; +import posthog from "posthog-js"; interface ShareButtonProps { isFullscreen: boolean; @@ -63,6 +64,7 @@ export default function ShareButton({ isFullscreen, viewerRef }: ShareButtonProp loading: "Copying link...", error: "Error copying link", }); + posthog.capture("paper_shared", { method: "link_copied" }); }} >

Copy Link To Clipboard

diff --git a/src/components/newPdfViewer.tsx b/src/components/newPdfViewer.tsx index 61895ba8..6556ec66 100644 --- a/src/components/newPdfViewer.tsx +++ b/src/components/newPdfViewer.tsx @@ -1,591 +1,718 @@ -"use client" -import { createPluginRegistration } from '@embedpdf/core'; -import { EmbedPDF } from '@embedpdf/core/react'; -import { usePdfiumEngine } from '@embedpdf/engines/react'; -import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react'; -import { useScroll, useScrollCapability, Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react'; -import { DocumentContent, DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react'; -import { useZoom, ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react'; -import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react'; -import { ExportPluginPackage } from '@embedpdf/plugin-export/react'; - -import { Download, ZoomIn, ZoomOut, Maximize2, Minimize2 } from "lucide-react"; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { downloadFile } from "../lib/utils/download"; -import { Button } from "./ui/button"; -import ShareButton from "./ShareButton"; -import ReportButton from "./ReportButton"; -import { useTheme } from "next-themes"; - -interface ControlProps { - documentId: string; - toggleFullscreen: () => void; - isFullscreen: boolean; - onDownload: () => Promise; - forceMobile?: boolean; - isMobile: boolean; - isSmall: boolean; - viewerRef: React.RefObject; -} - -interface PdfViewerProps { - url: string; - name: string; - className?: string; - height?: string; - hideControls?: boolean; - backgroundColor?: string; - hideScrollbar?: boolean; - isolateFromTheme?: boolean; -} - -interface WheelZoomProps { - documentId: string; - viewerRef: React.RefObject; -} - -function useBreakpoint() { - const [width, setWidth] = useState(null); - - useEffect(() => { - setWidth(window.innerWidth); - const handler = () => setWidth(window.innerWidth); - window.addEventListener("resize", handler); - return () => window.removeEventListener("resize", handler); - }, []); - - return {isMobile: width !== null && width < 768, isSmall: width !== null && width < 640}; -} - -const Controls = memo(function Controls({documentId, toggleFullscreen, isFullscreen, onDownload, - forceMobile, isMobile, isSmall, viewerRef}: ControlProps) { - - const { provides: zoomProv, state: zoomState } = useZoom(documentId); - const { provides: scrollProv, state: scrollState } = useScroll(documentId); - const { provides: scrollCapability } = useScrollCapability(); - const [pageNo, setPageNo] = useState("1"); - const [totalPages, setTotalPages] = useState(0); - const editingRef = useRef(false); - - useEffect(() => { - if (!scrollCapability) return; - const unsub = scrollCapability.onLayoutReady((event) => { - if (event.documentId === documentId) { - setTotalPages(event.totalPages); - } - }); - return () => { - unsub(); - setTotalPages(0); - }; - }, [scrollCapability, documentId]); - - useEffect(() => { - if (!scrollProv) return; - const unsub = scrollProv.onPageChange((event) => { - setTotalPages(event.totalPages); - if (!editingRef.current) { - setPageNo(String(event.pageNumber)); - } - }); - return () => unsub(); - }, [scrollProv]); - - const effectiveTotalPages = - totalPages > 0 ? totalPages : (scrollState?.totalPages ?? 0); - -const pageChange = useCallback( - (e: React.KeyboardEvent) => { - if (e.key !== "Enter") return; - const page = parseInt(pageNo, 10); - if (isNaN(page)) return; - if (page >= 1 && page <= effectiveTotalPages) { - scrollProv?.scrollToPage({ pageNumber: page, behavior: "smooth" }); - } else { - setPageNo(String(scrollProv?.getCurrentPage() ?? 1)); - } - }, - [pageNo, effectiveTotalPages, scrollProv] - ); - - if (!zoomProv || !scrollProv) return null; - - const zoomIn = () => zoomProv.zoomIn(); - const zoomOut = () => zoomProv.zoomOut(); - const { zoomLevel } = zoomState; - const fullScreenStyle = { - bottom: 20, - left: "50%", - transform: "translateX(-50%)", + "use client" + import { createPluginRegistration } from '@embedpdf/core'; + import { EmbedPDF } from '@embedpdf/core/react'; + import { usePdfiumEngine } from '@embedpdf/engines/react'; + import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react'; + import { useScroll, useScrollCapability, Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react'; + import { DocumentContent, DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react'; + import { useZoom, ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react'; + import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react'; + import { ExportPluginPackage } from '@embedpdf/plugin-export/react'; + + import { Download, ZoomIn, ZoomOut, Maximize2, Minimize2, BookOpenText, X } from "lucide-react"; + import posthog from "posthog-js"; + import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; + import { downloadFile } from "../lib/utils/download"; + import { Button } from "./ui/button"; + import ShareButton from "./ShareButton"; + import ReportButton from "./ReportButton"; + import { useTheme } from "next-themes"; + + interface ControlProps { + documentId: string; + toggleFullscreen: () => void; + isFullscreen: boolean; + onDownload: () => Promise; + forceMobile?: boolean; + isMobile: boolean; + isSmall: boolean; + viewerRef: React.RefObject; + isReadingMode: boolean; + toggleReadingMode: () => void; + showReadingCoachmark: boolean; + dismissReadingCoachmark: () => void; } - const pageInput = ( -
- { - editingRef.current = true; - setPageNo(e.target.value.replace(/[^0-9]/g, "")); - }} - onKeyDown={pageChange} - onFocus={(e) => { - editingRef.current = true; - e.target.select(); - }} - onBlur={() => { - editingRef.current = false; - setPageNo(String(scrollProv?.getCurrentPage() ?? 1)); - }} - inputMode="numeric" - className="h-9 w-14 rounded border bg-[#e7e9ff] p-1 text-center text-sm [appearance:textfield] dark:bg-[#1f1f2a] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" - /> - of {totalPages ?? 1} -
- ) - - const toolSet = ( - <> - + interface PdfViewerProps { + url: string; + name: string; + className?: string; + height?: string; + hideControls?: boolean; + backgroundColor?: string; + hideScrollbar?: boolean; + isolateFromTheme?: boolean; + onReadingModeChange?: (isReadingMode: boolean) => void; + } - + interface WheelZoomProps { + documentId: string; + viewerRef: React.RefObject; + } - + function useBreakpoint() { + const [width, setWidth] = useState(null); + + useEffect(() => { + setWidth(window.innerWidth); + const handler = () => setWidth(window.innerWidth); + window.addEventListener("resize", handler); + return () => window.removeEventListener("resize", handler); + }, []); + + return {isMobile: width !== null && width < 768, isSmall: width !== null && width < 640}; + } - - - - {typeof zoomLevel === "number" ? `${Math.round(zoomLevel * 100)}%` : "100%"} - - - + const Controls = memo(function Controls({documentId, toggleFullscreen, isFullscreen, onDownload, + forceMobile, isMobile, isSmall, viewerRef, isReadingMode, toggleReadingMode, + showReadingCoachmark, dismissReadingCoachmark}: ControlProps) { + + const { provides: zoomProv, state: zoomState } = useZoom(documentId); + const { provides: scrollProv, state: scrollState } = useScroll(documentId); + const { provides: scrollCapability } = useScrollCapability(); + const [pageNo, setPageNo] = useState("1"); + const [totalPages, setTotalPages] = useState(0); + const editingRef = useRef(false); + + useEffect(() => { + if (!scrollCapability) return; + const unsub = scrollCapability.onLayoutReady((event) => { + if (event.documentId === documentId) { + setTotalPages(event.totalPages); + } + }); + return () => { + unsub(); + setTotalPages(0); + }; + }, [scrollCapability, documentId]); + + useEffect(() => { + if (!scrollProv) return; + const unsub = scrollProv.onPageChange((event) => { + setTotalPages(event.totalPages); + if (!editingRef.current) { + setPageNo(String(event.pageNumber)); + } + }); + return () => unsub(); + }, [scrollProv]); + + const effectiveTotalPages = + totalPages > 0 ? totalPages : (scrollState?.totalPages ?? 0); + + const pageChange = useCallback( + (e: React.KeyboardEvent) => { + if (e.key !== "Enter") return; + const page = parseInt(pageNo, 10); + if (isNaN(page)) return; + if (page >= 1 && page <= effectiveTotalPages) { + scrollProv?.scrollToPage({ pageNumber: page, behavior: "smooth" }); + } else { + setPageNo(String(scrollProv?.getCurrentPage() ?? 1)); + } + }, + [pageNo, effectiveTotalPages, scrollProv] + ); + + if (!zoomProv || !scrollProv) return null; - {isSmall && } + if (isReadingMode) { + return ( + + ); + } - - ) + const zoomIn = () => zoomProv.zoomIn(); + const zoomOut = () => zoomProv.zoomOut(); + const { zoomLevel } = zoomState; + const fullScreenStyle = { + bottom: 20, + left: "50%", + transform: "translateX(-50%)", + } - if (!forceMobile) { - return ( + const pageInput = ( +
+ { + editingRef.current = true; + setPageNo(e.target.value.replace(/[^0-9]/g, "")); + }} + onKeyDown={pageChange} + onFocus={(e) => { + editingRef.current = true; + e.target.select(); + }} + onBlur={() => { + editingRef.current = false; + setPageNo(String(scrollProv?.getCurrentPage() ?? 1)); + }} + inputMode="numeric" + className="h-9 w-14 rounded border bg-[#e7e9ff] p-1 text-center text-sm [appearance:textfield] dark:bg-[#1f1f2a] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" + /> + of {totalPages ?? 1} +
+ ) + + const toolSet = ( <> - {!isSmall ? -
+ {showReadingCoachmark && ( +
+
+ New: Reading Mode. Hides + everything but the paper — lighter than Fullscreen, your tabs + stay put. + +
+
+
+ )} + {showReadingCoachmark && ( + + )} + +
+ +
: -
- {pageInput} + {isFullscreen ? : } + + + + + + + + + + {typeof zoomLevel === "number" ? `${Math.round(zoomLevel * 100)}%` : "100%"} + + + + + {isSmall && } + + + ) + + if (!forceMobile) { + return ( + <> + {!isSmall ?
{toolSet} -
- -
} - - - ); - } - - return( -
- {isSmall ? ( - <> - {pageInput} -
- {toolSet} -
- - ) : ( - <> - {toolSet} - {pageInput} - + {pageInput} + {!isSmall && } +
: +
+ {pageInput} +
+ {toolSet} +
+ +
} - )} -
- ) -}); - -function WheelZoom({ documentId, viewerRef }: WheelZoomProps) { - const { provides: zoomProv } = useZoom(documentId); - const accumulatedDelta = useRef(0); - const rafId = useRef(null); - const curZoom = useRef(1); - - useEffect(() => { - if (!zoomProv) return; - curZoom.current = zoomProv.getState().currentZoomLevel; - const unsub = zoomProv.onZoomChange((e) => { - curZoom.current = e.newZoom; - }); - return unsub; - }, [zoomProv]); - - const handleWheel = useCallback( - (e: WheelEvent) => { - if (!e.ctrlKey || !zoomProv) return; - e.preventDefault(); - - const isTrackpad = Math.abs(e.deltaY) < 50; - const scaleFactor = isTrackpad ? 0.008 : 0.08; - accumulatedDelta.current += -e.deltaY * scaleFactor; - - if (rafId.current !== null) cancelAnimationFrame(rafId.current); - rafId.current = requestAnimationFrame(() => { - const clamped = Math.max(-0.15, Math.min(accumulatedDelta.current, 0.15)); - - if (isTrackpad) { - const target = Math.max(0.25, Math.min(curZoom.current + clamped, 4)); - zoomProv.requestZoom(target) - curZoom.current = target; - } else { - zoomProv.requestZoomBy(clamped); - } - - accumulatedDelta.current = 0; - rafId.current = null; - }); - }, - [zoomProv] - ); - - useEffect(() => { - const viewer = viewerRef.current; - if (!viewer) return; - viewer.addEventListener("wheel", handleWheel, { passive: false }); - return () => { - viewer.removeEventListener("wheel", handleWheel); - if (rafId.current !== null) cancelAnimationFrame(rafId.current); - }; - }, [handleWheel, viewerRef]); - - return null; -} - -function useLoadingMessage(messages: string[], interval = 2200) { - const [index, setIndex] = useState(0); - const [visible, setVisible] = useState(true); - - useEffect(() => { - const timer = setInterval(() => { - setVisible(false); - setTimeout(() => { - setIndex(i => (i + 1) % messages.length); - setVisible(true); - }, 400); - }, interval); - return () => clearInterval(timer); - }, [messages, interval]); - - return { message: messages[index], visible }; -} - -const LOADING_MESSAGES = [ - "Loading document", - "Preparing pages", - "Almost there", -]; - -export function Loader({ - backgroundColor = "#070114", - textColor = "rgba(255,255,255,0.5)", -}: { - backgroundColor?: string; - textColor?: string; -}) { - const { message, visible } = useLoadingMessage(LOADING_MESSAGES); - return ( -
-
- - {message} - -
- ); -} -export default function PDFViewer({ - url, - name, - className, - height = "100dvh", - hideControls = false, - backgroundColor, - hideScrollbar = false, - isolateFromTheme = true, -}: PdfViewerProps) { - const { engine, isLoading } = usePdfiumEngine(); - const { isMobile, isSmall } = useBreakpoint(); - const { resolvedTheme } = useTheme(); - const [isFullscreen, setIsFullscreen] = useState(false); - const viewerRef = useRef(null); - const effectiveBackgroundColor = - backgroundColor ?? (resolvedTheme === "light" ? "#F3F5FF" : "#070114"); - const loaderTextColor = - resolvedTheme === "light" ? "rgba(17,24,39,0.6)" : "rgba(255,255,255,0.5)"; - - const handleDownload = useCallback(async () => { - window.dataLayer?.push({ - event: "pdf_download_start", - paper_title: name, - paper_url: url, - }); - await downloadFile(url, `${name}.pdf`); - }, [url, name]); - - const toggleFullscreen = useCallback(() => { - if (!document.fullscreenElement) { - void viewerRef.current?.requestFullscreen(); - } else { - void document.exitFullscreen(); + + ); } - }, []); - - useEffect(() => { - const handleFullscreenChange = () => - setIsFullscreen(!!document.fullscreenElement); - document.addEventListener("fullscreenchange", handleFullscreenChange); - return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); - }, []); - - const plugins = useMemo(() => [ - createPluginRegistration(DocumentManagerPluginPackage, { - initialDocuments: [{ url }], - }), - createPluginRegistration(ViewportPluginPackage), - createPluginRegistration(ScrollPluginPackage), - createPluginRegistration(RenderPluginPackage), - createPluginRegistration(ZoomPluginPackage, { - defaultZoomLevel: ZoomMode.FitPage, - }), - createPluginRegistration(ExportPluginPackage, { - defaultFileName: `${name}.pdf`, - }), - ], [url, name]); - -if (isLoading || !engine) { - return ( - - ); -} - - return ( - <> - {hideScrollbar && ( - - )} - {isolateFromTheme && ( - - )} + return(
+ {isSmall ? ( + <> + {pageInput} +
+ {toolSet} +
+ + ) : ( + <> + {toolSet} + {pageInput} + + + )} +
+ ) + }); + + function WheelZoom({ documentId, viewerRef }: WheelZoomProps) { + const { provides: zoomProv } = useZoom(documentId); + const accumulatedDelta = useRef(0); + const rafId = useRef(null); + const curZoom = useRef(1); + + useEffect(() => { + if (!zoomProv) return; + curZoom.current = zoomProv.getState().currentZoomLevel; + const unsub = zoomProv.onZoomChange((e) => { + curZoom.current = e.newZoom; + }); + return unsub; + }, [zoomProv]); + + const handleWheel = useCallback( + (e: WheelEvent) => { + if (!e.ctrlKey || !zoomProv) return; + e.preventDefault(); + + const isTrackpad = Math.abs(e.deltaY) < 50; + const scaleFactor = isTrackpad ? 0.008 : 0.08; + accumulatedDelta.current += -e.deltaY * scaleFactor; + + if (rafId.current !== null) cancelAnimationFrame(rafId.current); + rafId.current = requestAnimationFrame(() => { + const clamped = Math.max(-0.15, Math.min(accumulatedDelta.current, 0.15)); + + if (isTrackpad) { + const target = Math.max(0.25, Math.min(curZoom.current + clamped, 4)); + zoomProv.requestZoom(target) + curZoom.current = target; + } else { + zoomProv.requestZoomBy(clamped); + } + + accumulatedDelta.current = 0; + rafId.current = null; + }); + }, + [zoomProv] + ); + + useEffect(() => { + const viewer = viewerRef.current; + if (!viewer) return; + viewer.addEventListener("wheel", handleWheel, { passive: false }); + return () => { + viewer.removeEventListener("wheel", handleWheel); + if (rafId.current !== null) cancelAnimationFrame(rafId.current); + }; + }, [handleWheel, viewerRef]); + + return null; + } + + function useLoadingMessage(messages: string[], interval = 2200) { + const [index, setIndex] = useState(0); + const [visible, setVisible] = useState(true); + + useEffect(() => { + const timer = setInterval(() => { + setVisible(false); + setTimeout(() => { + setIndex(i => (i + 1) % messages.length); + setVisible(true); + }, 400); + }, interval); + return () => clearInterval(timer); + }, [messages, interval]); + + return { message: messages[index], visible }; + } + + const LOADING_MESSAGES = [ + "Loading document", + "Preparing pages", + "Almost there", + ]; + + export function Loader({ + backgroundColor = "#070114", + textColor = "rgba(255,255,255,0.5)", + }: { + backgroundColor?: string; + textColor?: string; + }) { + const { message, visible } = useLoadingMessage(LOADING_MESSAGES); + return ( +
- - {({ activeDocumentId }) => - activeDocumentId && ( - <> - - {!hideControls && (isMobile && !isFullscreen) && - } - - {({ isLoaded }) => ( - <> -
- -
- - ( -
e.stopPropagation()} - > - -
- )} - /> -
- - )} -
- - {!hideControls && (!isMobile || isFullscreen) && ( +
+ + {message} + +
+ ); + } + export default function PDFViewer({ + url, + name, + className, + height = "100dvh", + hideControls = false, + backgroundColor, + hideScrollbar = false, + isolateFromTheme = true, + onReadingModeChange, + }: PdfViewerProps) { + const { engine, isLoading } = usePdfiumEngine(); + const { isMobile, isSmall } = useBreakpoint(); + const { resolvedTheme } = useTheme(); + const [isFullscreen, setIsFullscreen] = useState(false); + const [isReadingMode, setIsReadingMode] = useState(false); + const [showReadingCoachmark, setShowReadingCoachmark] = useState(false); + const viewerRef = useRef(null); + const effectiveBackgroundColor = + backgroundColor ?? (resolvedTheme === "light" ? "#F3F5FF" : "#070114"); + const loaderTextColor = + resolvedTheme === "light" ? "rgba(17,24,39,0.6)" : "rgba(255,255,255,0.5)"; + + const handleDownload = useCallback(async () => { + window.dataLayer?.push({ + event: "pdf_download_start", + paper_title: name, + paper_url: url, + }); + posthog.capture("paper_downloaded", { + paper_name: name, + }); + await downloadFile(url, `${name}.pdf`); + }, [url, name]); + + const toggleFullscreen = useCallback(() => { + if (!document.fullscreenElement) { + void viewerRef.current?.requestFullscreen(); + } else { + void document.exitFullscreen(); + } + }, []); + + useEffect(() => { + const handleFullscreenChange = () => + setIsFullscreen(!!document.fullscreenElement); + document.addEventListener("fullscreenchange", handleFullscreenChange); + return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); + }, []); + + const dismissReadingCoachmark = useCallback(() => { + setShowReadingCoachmark(false); + try { + window.localStorage.setItem("pdfReadingModeSeen", "1"); + } catch { + // localStorage unavailable (private mode, etc) — coachmark just won't persist + } + }, []); + + const toggleReadingMode = useCallback(() => { + setIsReadingMode((r) => { + const next = !r; + posthog.capture("reading_mode_toggled", { enabled: next }); + onReadingModeChange?.(next); + return next; + }); + dismissReadingCoachmark(); + }, [dismissReadingCoachmark, onReadingModeChange]); + + useEffect(() => { + try { + if (!window.localStorage.getItem("pdfReadingModeSeen")) { + setShowReadingCoachmark(true); + } + } catch { + // ignore + } + }, []); + + useEffect(() => { + if (!isReadingMode) return; + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = prevOverflow; + }; + }, [isReadingMode]); + + useEffect(() => { + const handleKeydown = (e: KeyboardEvent) => { + const target = e.target as HTMLElement | null; + if (target && ["INPUT", "TEXTAREA"].includes(target.tagName)) return; + + if (e.key === "r" || e.key === "R") { + toggleReadingMode(); + } else if (e.key === "Escape" && isReadingMode) { + setIsReadingMode(false); + onReadingModeChange?.(false); + } + }; + window.addEventListener("keydown", handleKeydown); + return () => window.removeEventListener("keydown", handleKeydown); + }, [isReadingMode, toggleReadingMode, onReadingModeChange]); + + const plugins = useMemo(() => [ + createPluginRegistration(DocumentManagerPluginPackage, { + initialDocuments: [{ url }], + }), + createPluginRegistration(ViewportPluginPackage), + createPluginRegistration(ScrollPluginPackage), + createPluginRegistration(RenderPluginPackage), + createPluginRegistration(ZoomPluginPackage, { + defaultZoomLevel: ZoomMode.FitPage, + }), + createPluginRegistration(ExportPluginPackage, { + defaultFileName: `${name}.pdf`, + }), + ], [url, name]); + + if (isLoading || !engine) { + return ( + + ); + } + + return ( + <> + {hideScrollbar && ( + + )} + {isolateFromTheme && ( + + )} +
+ + {({ activeDocumentId }) => + activeDocumentId && ( + <> + + {!hideControls && (isMobile && !isFullscreen) && - )} - - ) - } - -
- - ); -} + isReadingMode={isReadingMode} + toggleReadingMode={toggleReadingMode} + showReadingCoachmark={showReadingCoachmark} + dismissReadingCoachmark={dismissReadingCoachmark} + />} + + {({ isLoaded }) => ( + <> +
+ +
+ + ( +
e.stopPropagation()} + > + +
+ )} + /> +
+ + )} +
+ + {!hideControls && (!isMobile || isFullscreen) && ( + + )} + + ) + } +
+
+ + ); + } \ No newline at end of file diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx index c7875272..832703c4 100644 --- a/src/components/ui/carousel.tsx +++ b/src/components/ui/carousel.tsx @@ -119,20 +119,32 @@ const Carousel = React.forwardRef< }; }, [api, onSelect]); + const contextValue = React.useMemo( + () => ({ + carouselRef, + api, + opts, + orientation: + orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), + scrollPrev, + scrollNext, + canScrollPrev, + canScrollNext, + }), + [ + carouselRef, + api, + opts, + orientation, + scrollPrev, + scrollNext, + canScrollPrev, + canScrollNext, + ], + ); + return ( - +
svg]:size-4 [&>svg]:shrink-0", + "relative flex cursor-default select-none items-center gap-2 rounded-xl px-2 py-1.5 text-sm text-black outline-none transition-colors hover:bg-black/[0.06] focus:bg-black/[0.06] dark:text-white dark:hover:bg-[#1F2A3D] dark:focus:bg-[#1F2A3D] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", inset && "pl-8", className, )} diff --git a/src/components/ui/sorting.tsx b/src/components/ui/sorting.tsx index b4f78cfc..dbeecab5 100644 --- a/src/components/ui/sorting.tsx +++ b/src/components/ui/sorting.tsx @@ -40,7 +40,7 @@ const SortComponent: React.FC = ({