SEO Dashboard Fixes and content planning refactoring

This commit is contained in:
ajaysi
2025-10-29 17:10:48 +05:30
parent 5866f49325
commit 4431cd9848
92 changed files with 7046 additions and 1940 deletions

View File

@@ -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"

View File

@@ -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",

View File

@@ -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;

View File

@@ -199,4 +199,4 @@ class GSCAPI {
}
}
export const gscAPI = new GSCAPI();
export const gscAPI = new GSCAPI();

View File

@@ -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

View File

@@ -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}</>;
};

View File

@@ -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}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 }}>

View File

@@ -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,