SEO Dashboard Fixes and content planning refactoring
This commit is contained in:
664
frontend/package-lock.json
generated
664
frontend/package-lock.json
generated
@@ -9,8 +9,9 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.46.1",
|
||||
"@copilotkit/react-core": "^1.10.3",
|
||||
"@copilotkit/react-ui": "^1.10.3",
|
||||
"@copilotkit/react-core": "^1.10.6",
|
||||
"@copilotkit/react-textarea": "^1.10.6",
|
||||
"@copilotkit/react-ui": "^1.10.6",
|
||||
"@copilotkit/shared": "^1.10.3",
|
||||
"@emotion/react": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
@@ -53,9 +54,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ag-ui/core": {
|
||||
"version": "0.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.36.tgz",
|
||||
"integrity": "sha512-uYUrzw6uxuw4qVQ61mdSeiG0mFh2n/VAWmWsWzwETDuhqJZT7rFmd07IajcFWcyItMr1wjqxFDdlklucAyEYNA==",
|
||||
"version": "0.0.37",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.37.tgz",
|
||||
"integrity": "sha512-7bmjPn1Ol0Zo00F+MrPr0eOwH4AFZbhmq/ZMhCsrMILtVYBiBLcLU9QFBpBL3Zm9MCHha8b79N7JE2FzwcMaVA==",
|
||||
"dependencies": {
|
||||
"rxjs": "7.8.1",
|
||||
"zod": "^3.22.4"
|
||||
@@ -2197,13 +2198,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/react-core": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/react-core/-/react-core-1.10.3.tgz",
|
||||
"integrity": "sha512-m/R/cUENBlXP7+E7TUImVPqmgrHtMeYd3/qhOK3hQY4LqCtbEG5ju5HkEy/QbmVX5tNn/Wo8ti0kwK9tXX6lzA==",
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/react-core/-/react-core-1.10.6.tgz",
|
||||
"integrity": "sha512-sdojpntwgOxP8lWRzaFEiWr0g2wDefjQHtve5GPPie+otseFonV88FZjSqIq5LN+q5BIwDOEhCmDjALsGjXvuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@copilotkit/runtime-client-gql": "1.10.3",
|
||||
"@copilotkit/shared": "1.10.3",
|
||||
"@copilotkit/runtime-client-gql": "1.10.6",
|
||||
"@copilotkit/shared": "1.10.6",
|
||||
"@scarf/scarf": "^1.3.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"untruncate-json": "^0.0.1"
|
||||
@@ -2213,15 +2214,66 @@
|
||||
"react-dom": "^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/react-ui": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/react-ui/-/react-ui-1.10.3.tgz",
|
||||
"integrity": "sha512-/MvKuVLor+372yKKs7Us3AmU2A/5+zWTE0Z0rEcMOxRNOPbjLt9zj5e86aJ0alz1hCspKG4UWCAGiphf+I19ig==",
|
||||
"node_modules/@copilotkit/react-textarea": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/react-textarea/-/react-textarea-1.10.6.tgz",
|
||||
"integrity": "sha512-04totNGPtBkfVdYy5rCBqn47HDbdd9cqHk49At0CD9DFmGOaL7kwMbywHj4Dqq6UpDKuJqnS9aYyLI073vuZwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@copilotkit/react-core": "1.10.3",
|
||||
"@copilotkit/runtime-client-gql": "1.10.3",
|
||||
"@copilotkit/shared": "1.10.3",
|
||||
"@copilotkit/react-core": "1.10.6",
|
||||
"@copilotkit/runtime-client-gql": "1.10.6",
|
||||
"@copilotkit/shared": "1.10.6",
|
||||
"@emotion/css": "^11.11.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.11",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"class-variance-authority": "^0.6.1",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lucide-react": "^0.274.0",
|
||||
"material-icons": "^1.13.10",
|
||||
"slate": "^0.94.1",
|
||||
"slate-history": "^0.93.0",
|
||||
"slate-react": "^0.98.1",
|
||||
"tailwind-merge": "^1.13.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/react-textarea/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/react-textarea/node_modules/lucide-react": {
|
||||
"version": "0.274.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.274.0.tgz",
|
||||
"integrity": "sha512-qiWcojRXEwDiSimMX1+arnxha+ROJzZjJaVvCC0rsG6a9pUPjZePXSq7em4ZKMp0NDm1hyzPNkM7UaWC3LU2AA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/react-ui": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/react-ui/-/react-ui-1.10.6.tgz",
|
||||
"integrity": "sha512-eNIbZKMvBVZqlAR4fqkmZRIYIt8WhwZOxfVJVwMD9nfmWdtatmxrOLecyDiPk/hkq2o/8s2/rubaZSMK6m+GHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@copilotkit/react-core": "1.10.6",
|
||||
"@copilotkit/runtime-client-gql": "1.10.6",
|
||||
"@copilotkit/shared": "1.10.6",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
@@ -2511,12 +2563,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@copilotkit/runtime-client-gql": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/runtime-client-gql/-/runtime-client-gql-1.10.3.tgz",
|
||||
"integrity": "sha512-c0pmm9vyK1gy7hYP8F7Me97CpfxUY7OBdWvI2JQh7oll4abL3w5IbpKIEr/UNOGGJFdgz0NJE8eDOwnadQ51ww==",
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/runtime-client-gql/-/runtime-client-gql-1.10.6.tgz",
|
||||
"integrity": "sha512-oLX8mjppVvQCWfquW9A0500hYVNxM4X/mtt76SEvfGUb2KsNQ4j2HOCzpmtm85MeLproC+f9738wLwRueLliZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@copilotkit/shared": "1.10.3",
|
||||
"@copilotkit/shared": "1.10.6",
|
||||
"@urql/core": "^5.0.3",
|
||||
"untruncate-json": "^0.0.1",
|
||||
"urql": "^4.1.0"
|
||||
@@ -2526,12 +2578,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@copilotkit/shared": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.10.3.tgz",
|
||||
"integrity": "sha512-LCgqfWfIfC97jCS6AMXVsuCXHK4CUqet7XXKJ9SB8gXR/kiDciP543gtoXznQK6L5ZP5FGDEH0KtPdNd/2Mcgg==",
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.10.6.tgz",
|
||||
"integrity": "sha512-56Rltf4fDBqCpl1ZXARypt5NdE4LTg3tGPPLurZpgPmm31Lv5EAHpfjC7I55vt9A0mXWlTCHtCrpiaAlTyzGJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ag-ui/core": "^0.0.36",
|
||||
"@ag-ui/core": "^0.0.37",
|
||||
"@segment/analytics-node": "^2.1.2",
|
||||
"chalk": "4.1.2",
|
||||
"graphql": "^16.8.1",
|
||||
@@ -2871,6 +2923,19 @@
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/css": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz",
|
||||
"integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/cache": "^11.13.5",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"@emotion/utils": "^1.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||
@@ -3592,6 +3657,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
||||
@@ -4025,8 +4096,7 @@
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.7",
|
||||
@@ -4099,7 +4169,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
@@ -4110,6 +4179,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
|
||||
"integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-focus-guards": "1.1.3",
|
||||
"@radix-ui/react-focus-scope": "1.1.7",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"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"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
||||
@@ -4131,7 +4236,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
|
||||
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
@@ -4159,7 +4263,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
|
||||
"integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
@@ -4175,7 +4278,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
|
||||
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
@@ -4201,7 +4303,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
||||
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
@@ -4215,6 +4316,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-label": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
|
||||
"integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.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"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||
@@ -4253,7 +4377,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
|
||||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
@@ -4273,12 +4396,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
@@ -4373,6 +4519,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-separator": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
|
||||
"integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.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"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz",
|
||||
@@ -4486,7 +4655,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
@@ -4502,7 +4670,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
||||
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
@@ -4522,7 +4689,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
|
||||
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
@@ -4541,7 +4707,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
|
||||
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
@@ -4560,7 +4725,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
@@ -5482,6 +5646,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/is-hotkey": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||
"integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||
@@ -5524,6 +5694,12 @@
|
||||
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
@@ -7277,7 +7453,6 @@
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
||||
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
@@ -8407,6 +8582,27 @@
|
||||
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.1.tgz",
|
||||
"integrity": "sha512-eurOEGc7YVx3majOrOb099PNKgO3KnKSApOprXI4BTq6bcfbqbQXPN2u+rPPmIJ2di23bMwhk0SxCCthBmszEQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "1.2.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://joebell.co.uk"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
@@ -8448,6 +8644,269 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.1.tgz",
|
||||
"integrity": "sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz",
|
||||
"integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.0",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-slot": "1.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
|
||||
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
|
||||
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
|
||||
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
||||
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
|
||||
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
|
||||
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/react-remove-scroll": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
|
||||
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
@@ -8670,6 +9129,12 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -9564,8 +10029,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-port-alt": {
|
||||
"version": "1.1.6",
|
||||
@@ -9648,6 +10112,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/direction": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
|
||||
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"direction": "cli.js"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
@@ -11595,7 +12072,6 @@
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -13128,6 +13604,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-hotkey": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
|
||||
"integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||
@@ -13213,6 +13695,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
@@ -14984,6 +15475,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/material-icons": {
|
||||
"version": "1.13.14",
|
||||
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.14.tgz",
|
||||
"integrity": "sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -18945,7 +19442,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
|
||||
"integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.7",
|
||||
"react-style-singleton": "^2.2.3",
|
||||
@@ -18971,7 +19467,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
|
||||
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-style-singleton": "^2.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
@@ -19099,7 +19594,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"get-nonce": "^1.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
@@ -20885,6 +21379,15 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "2.2.31",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
|
||||
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^1.0.20"
|
||||
}
|
||||
},
|
||||
"node_modules/select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@@ -21244,6 +21747,57 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slate": {
|
||||
"version": "0.94.1",
|
||||
"resolved": "https://registry.npmjs.org/slate/-/slate-0.94.1.tgz",
|
||||
"integrity": "sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-history": {
|
||||
"version": "0.93.0",
|
||||
"resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.93.0.tgz",
|
||||
"integrity": "sha512-Gr1GMGPipRuxIz41jD2/rbvzPj8eyar56TVMyJBvBeIpQSSjNISssvGNDYfJlSWM8eaRqf6DAcxMKzsLCYeX6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-react": {
|
||||
"version": "0.98.4",
|
||||
"resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.98.4.tgz",
|
||||
"integrity": "sha512-8Of3v9hFuX8rIRc86LuuBhU9t8ps+9ARKL4yyhCrKQYZ93Ep/LFA3GvPGvtf3zYuVadZ8tkhRH8tbHOGNAndLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@types/is-hotkey": "^0.1.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"direction": "^1.0.3",
|
||||
"is-hotkey": "^0.1.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"scroll-into-view-if-needed": "^2.2.20",
|
||||
"tiny-invariant": "1.0.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-react/node_modules/tiny-invariant": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
||||
"integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sockjs": {
|
||||
"version": "0.3.24",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
|
||||
@@ -22184,6 +22738,16 @@
|
||||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
@@ -22418,6 +22982,12 @@
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
@@ -23100,7 +23670,6 @@
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
@@ -23122,7 +23691,6 @@
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
||||
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-node-es": "^1.1.0",
|
||||
"tslib": "^2.0.0"
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.46.1",
|
||||
"@copilotkit/react-core": "^1.10.3",
|
||||
"@copilotkit/react-ui": "^1.10.3",
|
||||
"@copilotkit/react-core": "^1.10.6",
|
||||
"@copilotkit/react-textarea": "^1.10.6",
|
||||
"@copilotkit/react-ui": "^1.10.6",
|
||||
"@copilotkit/shared": "^1.10.3",
|
||||
"@emotion/react": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
|
||||
@@ -74,15 +74,15 @@ class CachedAnalyticsAPI {
|
||||
* Get analytics data with caching
|
||||
*/
|
||||
async getAnalyticsData(platforms?: string[], bypassCache: boolean = false): Promise<AnalyticsResponse> {
|
||||
const params = platforms ? { platforms: platforms.join(',') } : undefined;
|
||||
const baseParams: any = platforms ? { platforms: platforms.join(',') } : {};
|
||||
const endpoint = '/api/analytics/data';
|
||||
|
||||
// If bypassing cache, add timestamp to force fresh request
|
||||
const requestParams = bypassCache ? { ...params, _t: Date.now() } : params;
|
||||
const requestParams = bypassCache ? { ...baseParams, _t: Date.now() } : baseParams;
|
||||
|
||||
// Try to get from cache first (unless bypassing)
|
||||
if (!bypassCache) {
|
||||
const cached = analyticsCache.get<AnalyticsResponse>(endpoint, params);
|
||||
const cached = analyticsCache.get<AnalyticsResponse>(endpoint, baseParams);
|
||||
if (cached) {
|
||||
console.log('📦 Analytics Cache HIT: Analytics data (cached for 60 minutes)');
|
||||
return cached;
|
||||
@@ -95,7 +95,7 @@ class CachedAnalyticsAPI {
|
||||
|
||||
// Cache the result with extended TTL (unless bypassing)
|
||||
if (!bypassCache) {
|
||||
analyticsCache.set(endpoint, params, response.data, this.CACHE_TTL.ANALYTICS_DATA);
|
||||
analyticsCache.set(endpoint, baseParams, response.data, this.CACHE_TTL.ANALYTICS_DATA);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
|
||||
@@ -199,4 +199,4 @@ class GSCAPI {
|
||||
}
|
||||
}
|
||||
|
||||
export const gscAPI = new GSCAPI();
|
||||
export const gscAPI = new GSCAPI();
|
||||
@@ -21,6 +21,10 @@ export interface PlatformStatus {
|
||||
connected: boolean;
|
||||
last_sync?: string;
|
||||
data_points?: number;
|
||||
// Additional Bing-specific properties
|
||||
has_expired_tokens?: boolean;
|
||||
last_token_date?: string;
|
||||
total_tokens?: number;
|
||||
}
|
||||
|
||||
export interface AIInsight {
|
||||
@@ -40,6 +44,19 @@ export interface SEODashboardData {
|
||||
ai_insights: AIInsight[];
|
||||
last_updated: string;
|
||||
website_url?: string; // User's website URL from onboarding
|
||||
// Real data from backend
|
||||
summary?: {
|
||||
clicks: number;
|
||||
impressions: number;
|
||||
ctr: number;
|
||||
position: number;
|
||||
};
|
||||
timeseries?: any[];
|
||||
competitor_insights?: {
|
||||
competitor_keywords: any[];
|
||||
content_gaps: any[];
|
||||
opportunity_score: number;
|
||||
};
|
||||
}
|
||||
|
||||
// SEO Dashboard API functions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SEO CopilotKit Context Component
|
||||
// Provides real-time context and instructions to CopilotKit
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef, useMemo } from 'react';
|
||||
import { useCopilotReadable } from '@copilotkit/react-core';
|
||||
import { useSEOCopilotStore } from '../../stores/seoCopilotStore';
|
||||
|
||||
@@ -27,25 +27,29 @@ const SEOCopilotContext: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
}
|
||||
}, [personalizationData]);
|
||||
|
||||
// Memoize values to prevent unnecessary re-renders
|
||||
const websiteUrl = useMemo(() => analysisData?.url || '', [analysisData?.url]);
|
||||
const statusData = useMemo(() => ({
|
||||
isLoading,
|
||||
isAnalyzing,
|
||||
isGenerating,
|
||||
error
|
||||
}), [isLoading, isAnalyzing, isGenerating, error]);
|
||||
const suggestionsCount = useMemo(() => Array.isArray(suggestions) ? suggestions.length : 0, [suggestions]);
|
||||
|
||||
// Register SEO analysis data with CopilotKit
|
||||
useCopilotReadable({
|
||||
description: "Current SEO analysis data and insights",
|
||||
value: analysisData,
|
||||
categories: ["seo", "analysis"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered analysis data', !!analysisData);
|
||||
}
|
||||
|
||||
// Provide a flat, explicit website URL for the LLM
|
||||
useCopilotReadable({
|
||||
description: "Current website URL the user is working on",
|
||||
value: analysisData?.url || '',
|
||||
value: websiteUrl,
|
||||
categories: ["seo", "context"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered website URL', analysisData?.url);
|
||||
}
|
||||
|
||||
// Register personalization data with CopilotKit
|
||||
useCopilotReadable({
|
||||
@@ -53,9 +57,6 @@ const SEOCopilotContext: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
value: personalizationData,
|
||||
categories: ["user", "preferences"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered personalization', !!personalizationData);
|
||||
}
|
||||
|
||||
// Register dashboard layout with CopilotKit
|
||||
useCopilotReadable({
|
||||
@@ -63,9 +64,6 @@ const SEOCopilotContext: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
value: dashboardLayout,
|
||||
categories: ["ui", "layout"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered layout', !!dashboardLayout);
|
||||
}
|
||||
|
||||
// Register suggestions with CopilotKit
|
||||
useCopilotReadable({
|
||||
@@ -73,24 +71,25 @@ const SEOCopilotContext: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
value: suggestions,
|
||||
categories: ["actions", "suggestions"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered suggestions', Array.isArray(suggestions) ? suggestions.length : 0);
|
||||
}
|
||||
|
||||
// Register loading states with CopilotKit
|
||||
useCopilotReadable({
|
||||
description: "Current loading and processing states",
|
||||
value: {
|
||||
isLoading,
|
||||
isAnalyzing,
|
||||
isGenerating,
|
||||
error
|
||||
},
|
||||
value: statusData,
|
||||
categories: ["status", "loading"]
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered status', { isLoading, isAnalyzing, isGenerating, hasError: !!error });
|
||||
}
|
||||
|
||||
// Debug logging only in development and only when values actually change
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[CopilotContext] Registered analysis data', !!analysisData);
|
||||
console.log('[CopilotContext] Registered website URL', websiteUrl);
|
||||
console.log('[CopilotContext] Registered personalization', !!personalizationData);
|
||||
console.log('[CopilotContext] Registered layout', !!dashboardLayout);
|
||||
console.log('[CopilotContext] Registered suggestions', suggestionsCount);
|
||||
console.log('[CopilotContext] Registered status', { isLoading, isAnalyzing, isGenerating, hasError: !!error });
|
||||
}
|
||||
}, [analysisData, websiteUrl, personalizationData, dashboardLayout, suggestionsCount, statusData]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
@@ -7,10 +7,28 @@ import {
|
||||
Alert,
|
||||
Skeleton,
|
||||
Chip,
|
||||
Button
|
||||
Button,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Divider,
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useAuth, useUser, SignInButton, SignOutButton } from '@clerk/clerk-react';
|
||||
import { apiClient } from '../../api/client';
|
||||
import {
|
||||
Search as SearchIcon,
|
||||
Refresh as RefreshIcon,
|
||||
Person as PersonIcon,
|
||||
ExitToApp as ExitIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
MoreVert as MoreVertIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
Info as InfoIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
// Shared components
|
||||
import { DashboardContainer, GlassCard } from '../shared/styled';
|
||||
@@ -28,6 +46,14 @@ import { useSEODashboardStore } from '../../stores/seoDashboardStore';
|
||||
// API
|
||||
import { userDataAPI } from '../../api/userData';
|
||||
|
||||
// Shared components
|
||||
import PlatformAnalytics from '../shared/PlatformAnalytics';
|
||||
import { cachedAnalyticsAPI } from '../../api/cachedAnalytics';
|
||||
|
||||
// OAuth hooks
|
||||
import { useBingOAuth } from '../../hooks/useBingOAuth';
|
||||
import { useGSCConnection } from '../OnboardingWizard/common/useGSCConnection';
|
||||
|
||||
// SEO Dashboard component
|
||||
const SEODashboard: React.FC = () => {
|
||||
// Clerk authentication hooks
|
||||
@@ -51,6 +77,35 @@ const SEODashboard: React.FC = () => {
|
||||
getAnalysisFreshness,
|
||||
} = useSEODashboardStore();
|
||||
|
||||
// OAuth hooks
|
||||
const { connect: connectBing } = useBingOAuth();
|
||||
const { handleGSCConnect } = useGSCConnection();
|
||||
|
||||
// Platform status state
|
||||
const [platformStatus, setPlatformStatus] = useState({
|
||||
gsc: { connected: false, sites: [], last_sync: null, status: 'disconnected' },
|
||||
bing: {
|
||||
connected: false,
|
||||
sites: [],
|
||||
last_sync: null,
|
||||
status: 'disconnected',
|
||||
has_expired_tokens: false,
|
||||
last_token_date: undefined,
|
||||
total_tokens: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Menu state
|
||||
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
const [statusMenuAnchor, setStatusMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
|
||||
|
||||
// Competitor analysis data from onboarding step 3
|
||||
const [competitorAnalysisData, setCompetitorAnalysisData] = useState<any>(null);
|
||||
|
||||
// PlatformAnalytics refresh handle
|
||||
const platformRefreshRef = useRef<(() => Promise<void>) | null>(null);
|
||||
|
||||
// Sync dashboard analysis to Copilot store so readables have URL/context
|
||||
const setCopilotAnalysisData = useSEOCopilotStore(state => state.setAnalysisData);
|
||||
useEffect(() => {
|
||||
@@ -62,17 +117,112 @@ const SEODashboard: React.FC = () => {
|
||||
}
|
||||
}, [analysisData, setCopilotAnalysisData]);
|
||||
|
||||
// Load competitor analysis data on component mount
|
||||
useEffect(() => {
|
||||
// Simulate fetching dashboard data
|
||||
const fetchData = async () => {
|
||||
loadCompetitorAnalysisData();
|
||||
}, []);
|
||||
|
||||
// Reconnect handlers using existing OAuth hooks
|
||||
const handleGSCReconnect = async () => {
|
||||
try {
|
||||
console.log('Initiating GSC reconnect...');
|
||||
await handleGSCConnect();
|
||||
} catch (error) {
|
||||
console.error('Error reconnecting GSC:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBingReconnect = async () => {
|
||||
try {
|
||||
console.log('Initiating Bing reconnect...');
|
||||
// Purge expired tokens before reconnecting to avoid refresh loops
|
||||
try {
|
||||
await apiClient.post('/bing/purge-expired');
|
||||
console.log('Purged expired Bing tokens before reconnect');
|
||||
} catch (purgeError) {
|
||||
console.warn('Failed to purge expired tokens (non-critical):', purgeError);
|
||||
}
|
||||
await connectBing();
|
||||
// After successful reconnect, refresh platform status and run analysis
|
||||
try {
|
||||
// Invalidate backend analytics cache for Bing
|
||||
try {
|
||||
await apiClient.post('/api/analytics/cache/clear', null, { params: { platform: 'bing' } });
|
||||
console.log('Cleared backend analytics cache for Bing');
|
||||
} catch (cacheErr) {
|
||||
console.warn('Failed to clear backend analytics cache (non-critical):', cacheErr);
|
||||
}
|
||||
|
||||
// Invalidate frontend cached analytics
|
||||
try {
|
||||
cachedAnalyticsAPI.invalidatePlatformStatus();
|
||||
// Optional: clear all analytics cache if available
|
||||
// @ts-ignore - method may not exist in older builds
|
||||
cachedAnalyticsAPI.clearCache?.();
|
||||
console.log('Cleared frontend analytics cache');
|
||||
} catch (feCacheErr) {
|
||||
console.warn('Failed to clear frontend analytics cache (non-critical):', feCacheErr);
|
||||
}
|
||||
|
||||
await fetchPlatformStatus();
|
||||
} catch (e) {
|
||||
console.warn('Post-reconnect platform status refresh failed:', e);
|
||||
}
|
||||
try {
|
||||
await useSEODashboardStore.getState().refreshSEOAnalysis();
|
||||
} catch (e) {
|
||||
console.warn('Post-reconnect analysis refresh failed:', e);
|
||||
}
|
||||
|
||||
// Force PlatformAnalytics to refresh (bypass cache)
|
||||
try {
|
||||
await platformRefreshRef.current?.();
|
||||
} catch (e) {
|
||||
console.warn('Platform analytics forced refresh failed (non-critical):', e);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reconnecting Bing:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// One-run guard to avoid duplicate fetches under StrictMode
|
||||
const dataFetchedRef = useRef(false);
|
||||
|
||||
// Consolidated data fetching effect
|
||||
useEffect(() => {
|
||||
if (dataFetchedRef.current || !isSignedIn) return;
|
||||
dataFetchedRef.current = true;
|
||||
|
||||
const fetchAllData = async () => {
|
||||
let websiteUrl = 'https://alwrity.com'; // Default fallback
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Get user's website URL from user data
|
||||
const userData = await userDataAPI.getUserData();
|
||||
const websiteUrl = userData?.website_url || 'https://alwrity.com';
|
||||
// Fetch platform status and user data in parallel
|
||||
const [platformResponse, userData] = await Promise.all([
|
||||
apiClient.get('/api/seo-dashboard/platforms'),
|
||||
userDataAPI.getUserData()
|
||||
]);
|
||||
|
||||
// Mock data for demonstration
|
||||
console.log('Platform status response:', platformResponse.status, platformResponse.statusText);
|
||||
console.log('Platform status data:', platformResponse.data);
|
||||
setPlatformStatus(platformResponse.data);
|
||||
|
||||
websiteUrl = userData?.website_url || 'https://alwrity.com';
|
||||
|
||||
// Fetch real data from backend using authenticated API client
|
||||
console.log('Fetching SEO dashboard overview...');
|
||||
const response = await apiClient.get('/api/seo-dashboard/overview', {
|
||||
params: { site_url: websiteUrl }
|
||||
});
|
||||
|
||||
console.log('SEO overview response:', response.status, response.statusText);
|
||||
console.log('Real SEO data received:', response.data);
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching SEO dashboard data:', error);
|
||||
// Fallback to mock data on error
|
||||
const mockData = {
|
||||
health_score: {
|
||||
score: 84,
|
||||
@@ -118,26 +268,107 @@ const SEODashboard: React.FC = () => {
|
||||
last_updated: new Date().toISOString(),
|
||||
website_url: websiteUrl || undefined // Convert null to undefined for TypeScript
|
||||
};
|
||||
|
||||
setData(mockData);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setError('Failed to load dashboard data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
fetchAllData();
|
||||
}, [isSignedIn, setLoading, setData]);
|
||||
|
||||
useEffect(() => {
|
||||
// Run initial SEO analysis if no data exists
|
||||
if (!loading && !error && data) {
|
||||
// Call via store to avoid changing function identity in deps
|
||||
useSEODashboardStore.getState().checkAndRunInitialAnalysis();
|
||||
// Check if we have cached analysis data first
|
||||
const store = useSEODashboardStore.getState();
|
||||
store.checkAndRunInitialAnalysis();
|
||||
|
||||
// If no cached analysis data and we have a website URL, run initial analysis
|
||||
if (!store.analysisData && data.website_url) {
|
||||
console.log('No cached analysis data found, running initial SEO analysis...');
|
||||
store.runSEOAnalysis();
|
||||
}
|
||||
}
|
||||
}, [loading, error, data]);
|
||||
|
||||
// Menu handlers
|
||||
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setUserMenuAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleUserMenuClose = () => {
|
||||
setUserMenuAnchor(null);
|
||||
};
|
||||
|
||||
const handleStatusMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setStatusMenuAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleStatusMenuClose = () => {
|
||||
setStatusMenuAnchor(null);
|
||||
};
|
||||
|
||||
const handleBackToDashboard = () => {
|
||||
window.location.href = '/seo-dashboard';
|
||||
};
|
||||
|
||||
const handleRefreshData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await refreshSEOAnalysis();
|
||||
await fetchPlatformStatus();
|
||||
setLastRefresh(new Date());
|
||||
} catch (error) {
|
||||
console.error('Error refreshing data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Background jobs visibility (user-triggered)
|
||||
const [showBackgroundJobs, setShowBackgroundJobs] = useState(false);
|
||||
|
||||
// Platform status fetching function
|
||||
const fetchPlatformStatus = async () => {
|
||||
try {
|
||||
console.log('Fetching platform status...');
|
||||
const response = await apiClient.get('/api/seo-dashboard/platforms');
|
||||
console.log('Platform status response:', response.status, response.statusText);
|
||||
console.log('Platform status data:', response.data);
|
||||
setPlatformStatus(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching platform status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Load competitor analysis data from onboarding step 3
|
||||
const loadCompetitorAnalysisData = () => {
|
||||
try {
|
||||
const cachedData = localStorage.getItem('competitor_analysis_data');
|
||||
const cachedUrl = localStorage.getItem('competitor_analysis_url');
|
||||
const cachedTimestamp = localStorage.getItem('competitor_analysis_timestamp');
|
||||
|
||||
if (cachedData && cachedUrl && cachedTimestamp) {
|
||||
const analysisData = JSON.parse(cachedData);
|
||||
const timestamp = parseInt(cachedTimestamp);
|
||||
const isRecent = (Date.now() - timestamp) < (7 * 24 * 60 * 60 * 1000); // 7 days
|
||||
|
||||
if (isRecent) {
|
||||
console.log('Loading competitor analysis data from onboarding step 3:', analysisData);
|
||||
setCompetitorAnalysisData(analysisData);
|
||||
} else {
|
||||
console.log('Competitor analysis data is too old, not loading');
|
||||
}
|
||||
} else {
|
||||
console.log('No competitor analysis data found in localStorage');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading competitor analysis data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (loading) {
|
||||
return <Skeleton variant="rectangular" height={200} />;
|
||||
}
|
||||
@@ -202,137 +433,445 @@ const SEODashboard: React.FC = () => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box sx={{ mb: 4, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box>
|
||||
<Typography variant="h4" sx={{ color: 'white', fontWeight: 700 }}>
|
||||
🔍 SEO Dashboard
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
AI-powered insights and actionable recommendations
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* Professional Compact Header */}
|
||||
<Box sx={{
|
||||
mb: 4,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
py: 2,
|
||||
px: 3,
|
||||
bgcolor: 'rgba(255, 255, 255, 0.05)',
|
||||
borderRadius: 2,
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)'
|
||||
}}>
|
||||
{/* Left Section - Navigation & Title */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{/* User Info */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Chip
|
||||
label={`Signed in as ${user?.primaryEmailAddress?.emailAddress || 'User'}`}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: 'rgba(76, 175, 80, 0.25)',
|
||||
border: '1px solid rgba(76, 175, 80, 0.45)',
|
||||
color: 'white',
|
||||
fontWeight: 600
|
||||
}}
|
||||
/>
|
||||
<SignOutButton>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{
|
||||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
borderColor: 'rgba(255, 255, 255, 0.5)',
|
||||
bgcolor: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</SignOutButton>
|
||||
</Box>
|
||||
|
||||
{/* Freshness Indicator */}
|
||||
{(() => {
|
||||
const freshness = getAnalysisFreshness();
|
||||
const chipColor = freshness.isStale ? 'rgba(255, 193, 7, 0.25)' : 'rgba(76, 175, 80, 0.25)';
|
||||
const chipBorder = freshness.isStale ? 'rgba(255, 193, 7, 0.45)' : 'rgba(76, 175, 80, 0.45)';
|
||||
return (
|
||||
<Chip
|
||||
label={`Freshness: ${freshness.label}`}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: chipColor,
|
||||
border: `1px solid ${chipBorder}`,
|
||||
color: 'white',
|
||||
fontWeight: 600
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
<Button
|
||||
onClick={refreshSEOAnalysis}
|
||||
disabled={analysisLoading}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{
|
||||
<IconButton
|
||||
onClick={handleBackToDashboard}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderColor: 'rgba(255, 255, 255, 0.6)',
|
||||
'&:hover': { borderColor: 'rgba(255, 255, 255, 0.9)' }
|
||||
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
|
||||
}}
|
||||
>
|
||||
{analysisLoading ? 'Refreshing…' : 'Refresh'}
|
||||
</Button>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ color: 'white', fontWeight: 700, lineHeight: 1.2 }}>
|
||||
SEO Dashboard
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
AI-powered insights and recommendations
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Center Section - Status Overview */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Tooltip title="Platform Connection Status">
|
||||
<IconButton
|
||||
onClick={handleStatusMenuOpen}
|
||||
sx={{
|
||||
color: 'white',
|
||||
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
|
||||
}}
|
||||
>
|
||||
<CheckCircleIcon sx={{
|
||||
color: platformStatus.gsc.connected && platformStatus.bing.connected
|
||||
? '#4CAF50'
|
||||
: platformStatus.gsc.connected || platformStatus.bing.connected
|
||||
? '#FF9800'
|
||||
: '#f44336'
|
||||
}} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Data Freshness">
|
||||
<Chip
|
||||
icon={<ScheduleIcon />}
|
||||
label={(() => {
|
||||
const freshness = getAnalysisFreshness();
|
||||
return freshness.label;
|
||||
})()}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
||||
color: 'white',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
fontSize: '0.75rem'
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* Right Section - User Menu */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Avatar sx={{ width: 32, height: 32, bgcolor: 'rgba(33, 150, 243, 0.8)' }}>
|
||||
<PersonIcon fontSize="small" />
|
||||
</Avatar>
|
||||
|
||||
<IconButton
|
||||
onClick={handleUserMenuOpen}
|
||||
sx={{
|
||||
color: 'white',
|
||||
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
|
||||
}}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
{/* Status Menu */}
|
||||
<Menu
|
||||
anchorEl={statusMenuAnchor}
|
||||
open={Boolean(statusMenuAnchor)}
|
||||
onClose={handleStatusMenuClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
bgcolor: 'rgba(30, 30, 30, 0.95)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
color: 'white',
|
||||
minWidth: 280
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem disabled>
|
||||
<Typography variant="subtitle2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
Platform Status
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
|
||||
{/* GSC Status */}
|
||||
<MenuItem>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon sx={{
|
||||
color: platformStatus.gsc.connected ? '#4CAF50' : '#f44336',
|
||||
fontSize: 16
|
||||
}} />
|
||||
<Typography variant="body2">
|
||||
Google Search Console: {platformStatus.gsc.connected ? 'Connected' : 'Disconnected'}
|
||||
</Typography>
|
||||
</Box>
|
||||
{!platformStatus.gsc.connected && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={handleGSCReconnect}
|
||||
sx={{
|
||||
ml: 2,
|
||||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||||
color: 'white',
|
||||
fontSize: '0.75rem',
|
||||
'&:hover': {
|
||||
borderColor: 'rgba(255, 255, 255, 0.5)',
|
||||
bgcolor: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Reconnect
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
|
||||
{/* Bing Status */}
|
||||
<MenuItem>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon sx={{
|
||||
color: platformStatus.bing.connected ? '#4CAF50' :
|
||||
platformStatus.bing.status === 'expired' ? '#FF9800' : '#f44336',
|
||||
fontSize: 16
|
||||
}} />
|
||||
<Box>
|
||||
<Typography variant="body2">
|
||||
Bing Webmaster: {platformStatus.bing.connected ? 'Connected' :
|
||||
platformStatus.bing.status === 'expired' ? 'Expired' : 'Disconnected'}
|
||||
</Typography>
|
||||
{platformStatus.bing.status === 'expired' && platformStatus.bing.last_token_date && (
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '0.7rem' }}>
|
||||
Last connected: {new Date(platformStatus.bing.last_token_date).toLocaleDateString()}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{!platformStatus.bing.connected && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={handleBingReconnect}
|
||||
sx={{
|
||||
ml: 2,
|
||||
borderColor: platformStatus.bing.status === 'expired' ? '#FF9800' : 'rgba(255, 255, 255, 0.3)',
|
||||
color: platformStatus.bing.status === 'expired' ? '#FF9800' : 'white',
|
||||
fontSize: '0.75rem',
|
||||
'&:hover': {
|
||||
borderColor: platformStatus.bing.status === 'expired' ? '#FFB74D' : 'rgba(255, 255, 255, 0.5)',
|
||||
bgcolor: platformStatus.bing.status === 'expired' ? 'rgba(255, 152, 0, 0.1)' : 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{platformStatus.bing.status === 'expired' ? 'Reconnect' : 'Connect'}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* User Menu */}
|
||||
<Menu
|
||||
anchorEl={userMenuAnchor}
|
||||
open={Boolean(userMenuAnchor)}
|
||||
onClose={handleUserMenuClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
bgcolor: 'rgba(30, 30, 30, 0.95)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
color: 'white'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem disabled>
|
||||
<Typography variant="subtitle2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
{user?.primaryEmailAddress?.emailAddress || 'User'}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
<Divider sx={{ bgcolor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<MenuItem onClick={handleRefreshData}>
|
||||
<RefreshIcon sx={{ mr: 1, fontSize: 16 }} />
|
||||
<Typography variant="body2">Refresh Data</Typography>
|
||||
</MenuItem>
|
||||
<Divider sx={{ bgcolor: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<SignOutButton>
|
||||
<MenuItem>
|
||||
<ExitIcon sx={{ mr: 1, fontSize: 16 }} />
|
||||
<Typography variant="body2">Sign Out</Typography>
|
||||
</MenuItem>
|
||||
</SignOutButton>
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
{/* GSC Connection Section */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<GSCLoginButton />
|
||||
</Box>
|
||||
|
||||
{/* CopilotKit Test Panel removed */}
|
||||
|
||||
{/* Executive Summary */}
|
||||
{/* Search Performance Overview */}
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
|
||||
📊 Performance Overview
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<GlassCard sx={{ p: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
Organic Traffic
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ color: '#4CAF50' }}>
|
||||
{data.metrics.traffic.value}
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
|
||||
📊 Search Performance Overview
|
||||
</Typography>
|
||||
<Tooltip title="Real-time analytics data from connected search platforms">
|
||||
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => setShowBackgroundJobs((v) => !v)}
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
{showBackgroundJobs ? 'Hide Background Jobs' : 'Run Background Jobs'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<PlatformAnalytics
|
||||
platforms={['gsc', 'bing']}
|
||||
showSummary={true}
|
||||
refreshInterval={0}
|
||||
onDataLoaded={(analyticsData) => {
|
||||
console.log('Real analytics data loaded:', analyticsData);
|
||||
}}
|
||||
onRefreshReady={(fn) => { platformRefreshRef.current = fn; }}
|
||||
onReconnect={(platform) => {
|
||||
if (platform === 'gsc') {
|
||||
handleGSCReconnect();
|
||||
} else if (platform === 'bing') {
|
||||
handleBingReconnect();
|
||||
}
|
||||
}}
|
||||
showBackgroundJobs={showBackgroundJobs}
|
||||
/>
|
||||
|
||||
{/* Enhanced Metrics with Tooltips */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Tooltip title="Number of search engine platforms (GSC, Bing) currently connected to your dashboard">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Connected Platforms
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#4CAF50', fontWeight: 700 }}>
|
||||
{(platformStatus.gsc.connected ? 1 : 0) + (platformStatus.bing.connected ? 1 : 0)}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
of 2 platforms
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Tooltip title="Total number of clicks from search results to your website within the selected time period">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Total Clicks
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#2196F3', fontWeight: 700 }}>
|
||||
{data.metrics?.traffic?.value || data.summary?.clicks || 0}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
from search results
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Tooltip title="Total number of times your website appeared in search results within the selected time period">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Total Impressions
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#FF9800', fontWeight: 700 }}>
|
||||
{data.metrics?.impressions?.value || data.summary?.impressions || 0}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
search appearances
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Tooltip title="Percentage of impressions that resulted in a click to your website (Clicks ÷ Impressions × 100)">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Overall CTR
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#9C27B0', fontWeight: 700 }}>
|
||||
{data.metrics?.ctr?.value || data.summary?.ctr || 0}%
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
click-through rate
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<GlassCard sx={{ p: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
Average Ranking
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ color: '#2196F3' }}>
|
||||
{data.metrics.rankings.value}
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<GlassCard sx={{ p: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
Mobile Speed
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ color: '#FF9800' }}>
|
||||
{data.metrics.mobile.value}
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<GlassCard sx={{ p: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||
Keywords Tracked
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ color: '#9C27B0' }}>
|
||||
{data.metrics.keywords.value}
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Competitive Analysis from Onboarding Step 3 */}
|
||||
{competitorAnalysisData && (
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
|
||||
🎯 Competitive Analysis
|
||||
</Typography>
|
||||
<Tooltip title="Real competitor analysis data from onboarding step 3">
|
||||
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Tooltip title="Number of competitors discovered during onboarding analysis">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Competitors Found
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#4CAF50', fontWeight: 700 }}>
|
||||
{competitorAnalysisData.competitors?.length || 0}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
in your market
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<Tooltip title="Social media accounts discovered for competitors">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Social Media Accounts
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#2196F3', fontWeight: 700 }}>
|
||||
{Object.keys(competitorAnalysisData.social_media_accounts || {}).length}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
competitor accounts
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<Tooltip title="Social media citations and mentions found">
|
||||
<GlassCard sx={{ p: 2, cursor: 'help' }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
Social Citations
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ color: '#FF9800', fontWeight: 700 }}>
|
||||
{competitorAnalysisData.social_media_citations?.length || 0}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
mentions found
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Competitor List */}
|
||||
{competitorAnalysisData.competitors && competitorAnalysisData.competitors.length > 0 && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
|
||||
Top Competitors
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{competitorAnalysisData.competitors.slice(0, 6).map((competitor: any, index: number) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||
<GlassCard sx={{ p: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 600, mb: 1 }}>
|
||||
{competitor.name || competitor.domain || `Competitor ${index + 1}`}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
|
||||
{competitor.domain || competitor.url || 'No domain available'}
|
||||
</Typography>
|
||||
{competitor.description && (
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
{competitor.description.length > 100
|
||||
? `${competitor.description.substring(0, 100)}...`
|
||||
: competitor.description}
|
||||
</Typography>
|
||||
)}
|
||||
</GlassCard>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Research Summary */}
|
||||
{competitorAnalysisData.research_summary && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
|
||||
Research Summary
|
||||
</Typography>
|
||||
<GlassCard sx={{ p: 3 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.9)', lineHeight: 1.6 }}>
|
||||
{competitorAnalysisData.research_summary}
|
||||
</Typography>
|
||||
</GlassCard>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* SEO Analyzer Panel */}
|
||||
<SEOAnalyzerPanel
|
||||
analysisData={analysisData}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '@mui/icons-material';
|
||||
import { useAuth } from '@clerk/clerk-react';
|
||||
import { gscAPI, GSCStatusResponse } from '../../../api/gsc';
|
||||
import { apiClient } from '../../../api/client';
|
||||
|
||||
interface GSCLoginButtonProps {
|
||||
onStatusChange?: (connected: boolean) => void;
|
||||
@@ -69,17 +70,28 @@ const GSCLoginButton: React.FC<GSCLoginButtonProps> = ({ onStatusChange }) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const statusResponse = await gscAPI.getStatus();
|
||||
setStatus(statusResponse);
|
||||
// Use backend API to check GSC status
|
||||
const response = await apiClient.get('/api/seo-dashboard/platforms');
|
||||
const platformData = response.data;
|
||||
|
||||
const gscStatus = {
|
||||
connected: platformData.gsc?.connected || false,
|
||||
sites: platformData.gsc?.sites || [],
|
||||
last_sync: platformData.gsc?.last_sync || undefined
|
||||
};
|
||||
|
||||
setStatus(gscStatus);
|
||||
|
||||
if (onStatusChange) {
|
||||
onStatusChange(statusResponse.connected);
|
||||
onStatusChange(gscStatus.connected);
|
||||
}
|
||||
|
||||
console.log('GSC Login Button: Status checked, connected:', statusResponse.connected);
|
||||
console.log('GSC Login Button: Status checked, connected:', gscStatus.connected);
|
||||
} catch (err) {
|
||||
console.error('GSC Login Button: Error checking status:', err);
|
||||
setError('Failed to check GSC connection status');
|
||||
// Set disconnected status on error
|
||||
setStatus({ connected: false, sites: [], last_sync: undefined });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -206,8 +206,14 @@ const BackgroundJobManager: React.FC<BackgroundJobManagerProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// One-run guard to prevent duplicate calls in StrictMode
|
||||
const jobsFetchedRef = useRef(false);
|
||||
|
||||
// Poll for job updates
|
||||
useEffect(() => {
|
||||
if (jobsFetchedRef.current) return;
|
||||
jobsFetchedRef.current = true;
|
||||
|
||||
fetchJobs();
|
||||
|
||||
// Only start polling if there are running jobs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Error as ErrorIcon,
|
||||
Warning,
|
||||
} from '@mui/icons-material';
|
||||
import { Button } from '@mui/material';
|
||||
import { PlatformAnalytics as PlatformAnalyticsType, AnalyticsSummary, PlatformConnectionStatus } from '../../api/analytics';
|
||||
import { cachedAnalyticsAPI } from '../../api/cachedAnalytics';
|
||||
import BingInsightsCard from './BingInsightsCard';
|
||||
@@ -37,6 +38,8 @@ interface PlatformAnalyticsComponentProps {
|
||||
refreshInterval?: number; // in milliseconds, 0 = no auto-refresh
|
||||
onDataLoaded?: (data: any) => void;
|
||||
onRefreshReady?: (refreshFn: () => Promise<void>) => void; // Expose refresh function to parent
|
||||
onReconnect?: (platform: string) => void; // Reconnect handler for individual platforms
|
||||
showBackgroundJobs?: boolean; // Only render background jobs when user triggers
|
||||
}
|
||||
|
||||
const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
|
||||
@@ -45,6 +48,8 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
|
||||
refreshInterval = 0,
|
||||
onDataLoaded,
|
||||
onRefreshReady,
|
||||
onReconnect,
|
||||
showBackgroundJobs = false,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -111,7 +116,13 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
|
||||
}
|
||||
}, [platforms, loadData]);
|
||||
|
||||
// One-run guard to prevent duplicate calls in StrictMode
|
||||
const dataLoadedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataLoadedRef.current) return;
|
||||
dataLoadedRef.current = true;
|
||||
|
||||
loadData();
|
||||
|
||||
// Set up auto-refresh if interval is specified
|
||||
@@ -300,9 +311,31 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
|
||||
)}
|
||||
|
||||
{data.status === 'error' && (
|
||||
<Alert severity="error" sx={{ mt: 1 }}>
|
||||
{data.error_message || 'Failed to load analytics data'}
|
||||
</Alert>
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{data.error_message || 'Failed to load analytics data'}
|
||||
</Alert>
|
||||
{onReconnect && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
onClick={() => onReconnect(platform)}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
fontWeight: 600,
|
||||
borderColor: '#f44336',
|
||||
color: '#f44336',
|
||||
'&:hover': {
|
||||
borderColor: '#d32f2f',
|
||||
backgroundColor: 'rgba(244, 67, 54, 0.04)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Reconnect {platform.toUpperCase()}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{data.status === 'partial' && (
|
||||
@@ -423,18 +456,20 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Background Job Manager */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<BackgroundJobManager
|
||||
siteUrl="https://www.alwrity.com/"
|
||||
days={30}
|
||||
onJobCompleted={(job) => {
|
||||
console.log('🎉 Background job completed:', job);
|
||||
// Refresh analytics data when job completes
|
||||
forceRefresh();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Background Job Manager - render only when explicitly enabled */}
|
||||
{showBackgroundJobs && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<BackgroundJobManager
|
||||
siteUrl="https://www.alwrity.com/"
|
||||
days={30}
|
||||
onJobCompleted={(job) => {
|
||||
console.log('🎉 Background job completed:', job);
|
||||
// Refresh analytics data when job completes
|
||||
forceRefresh();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Debug Section - Show data structure for all platforms */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
|
||||
@@ -111,6 +111,9 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
|
||||
throw new Error('Failed to open Bing OAuth popup. Please allow popups for this site.');
|
||||
}
|
||||
|
||||
// Track if we've already handled success/error to avoid duplicate processing
|
||||
let messageHandled = false;
|
||||
|
||||
// Listen for popup completion and messages
|
||||
const messageHandler = (event: MessageEvent) => {
|
||||
console.log('Bing OAuth: Message received from any source:', {
|
||||
@@ -139,6 +142,7 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
|
||||
|
||||
if (event.data?.type === 'BING_OAUTH_SUCCESS') {
|
||||
console.log('Bing OAuth: Success message received:', event.data);
|
||||
messageHandled = true;
|
||||
popup.close();
|
||||
window.removeEventListener('message', messageHandler);
|
||||
|
||||
@@ -148,6 +152,7 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
|
||||
}, 1000);
|
||||
} else if (event.data?.type === 'BING_OAUTH_ERROR') {
|
||||
console.error('Bing OAuth: Error message received:', event.data);
|
||||
messageHandled = true;
|
||||
popup.close();
|
||||
window.removeEventListener('message', messageHandler);
|
||||
setError(event.data.error || 'Bing OAuth connection failed');
|
||||
@@ -170,7 +175,13 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
|
||||
clearInterval(checkClosed);
|
||||
window.removeEventListener('message', messageHandler);
|
||||
console.log('Bing OAuth: Popup closed, refreshing status...');
|
||||
console.log('Bing OAuth: Popup closed without receiving success/error message');
|
||||
|
||||
if (!messageHandled) {
|
||||
console.log('Bing OAuth: Popup closed without receiving success/error message');
|
||||
} else {
|
||||
console.log('Bing OAuth: Popup closed after successful message handling');
|
||||
}
|
||||
|
||||
// Refresh status after OAuth completion
|
||||
setTimeout(() => {
|
||||
checkStatus();
|
||||
@@ -217,10 +228,7 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
// Check status on mount
|
||||
useEffect(() => {
|
||||
checkStatus();
|
||||
}, [checkStatus]);
|
||||
// Note: Status check is now handled by the parent component to avoid duplicate API calls
|
||||
|
||||
return {
|
||||
connected,
|
||||
|
||||
Reference in New Issue
Block a user