Compare commits
4 الالتزامات
954fc4c34a
...
feature/ke
| المؤلف | SHA1 | التاريخ | |
|---|---|---|---|
| e9d6cd1562 | |||
| db38660fc9 | |||
| e4ad2acf6c | |||
| 1131dc8527 |
15
package-lock.json
مولّد
15
package-lock.json
مولّد
@@ -11,12 +11,15 @@
|
||||
"@apollo/client": "^4.0.7",
|
||||
"@apollo/react-hooks": "^4.0.0",
|
||||
"@nhost/react": "^3.11.2",
|
||||
"@reduxjs/toolkit": "^2.9.2",
|
||||
"and": "^0.0.3",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-ws": "^6.0.6",
|
||||
"lucide-react": "^0.544.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.9.1",
|
||||
"recharts": "^3.2.1"
|
||||
},
|
||||
@@ -1275,9 +1278,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
|
||||
"integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.2.tgz",
|
||||
"integrity": "sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
@@ -2165,6 +2168,12 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/and": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/and/-/and-0.0.3.tgz",
|
||||
"integrity": "sha512-ns0tgq+jQFbDhAu5wrE+M7V87E4L3N8ZTJfRKVxafi7g0BSEM6JnwhL57HyR1XMHBeRfGPWZxq+SH1bZeUTiTw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
|
||||
@@ -13,12 +13,15 @@
|
||||
"@apollo/client": "^4.0.7",
|
||||
"@apollo/react-hooks": "^4.0.0",
|
||||
"@nhost/react": "^3.11.2",
|
||||
"@reduxjs/toolkit": "^2.9.2",
|
||||
"and": "^0.0.3",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-ws": "^6.0.6",
|
||||
"lucide-react": "^0.544.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.9.1",
|
||||
"recharts": "^3.2.1"
|
||||
},
|
||||
|
||||
ثنائية
src/assets/bgoverherosection.png
Normal file
ثنائية
src/assets/bgoverherosection.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 1.1 MiB |
@@ -59,93 +59,93 @@
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_11710_20413" x="-66.8119" y="-21.1381" width="1705.63" height="768.747" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter0_f_11710_20413" x="-66.8119" y="-21.1381" width="1705.63" height="768.747" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="3.66938" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_11710_20413" x="-130.007" y="-38.9609" width="1786.65" height="804.393" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter1_f_11710_20413" x="-130.007" y="-38.9609" width="1786.65" height="804.393" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="12.5807" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_11710_20413" x="397.003" y="-22.1395" width="981.037" height="695.053" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter2_f_11710_20413" x="397.003" y="-22.1395" width="981.037" height="695.053" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="85.9684" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_11710_20413" x="-9.84216" y="-42.0887" width="788.344" height="267.388" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter3_f_11710_20413" x="-9.84216" y="-42.0887" width="788.344" height="267.388" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="35.6454" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_11710_20413" x="90.5024" y="4.36978" width="587.656" height="174.471" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter4_f_11710_20413" x="90.5024" y="4.36978" width="587.656" height="174.471" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="18.8711" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_11710_20413" x="9.45373" y="157.608" width="1664.2" height="800.466" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter5_f_11710_20413" x="9.45373" y="157.608" width="1664.2" height="800.466" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="5.76618" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter6_f_11710_20413" x="-200.828" y="159.532" width="1911.18" height="835.236" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter6_f_11710_20413" x="-200.828" y="159.532" width="1911.18" height="835.236" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="24.1131" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter7_f_11710_20413" x="-206.243" y="146.664" width="981.037" height="695.053" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter7_f_11710_20413" x="-206.243" y="146.664" width="981.037" height="695.053" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="85.9684" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter8_f_11710_20413" x="337.714" y="222.014" width="985.891" height="760.418" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter8_f_11710_20413" x="337.714" y="222.014" width="985.891" height="760.418" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="85.9684" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter9_f_11710_20413" x="892.519" y="71.8993" width="981.037" height="695.053" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter9_f_11710_20413" x="892.519" y="71.8993" width="981.037" height="695.053" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="85.9684" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter10_f_11710_20413" x="-507.711" y="193.054" width="924.028" height="756.825" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter10_f_11710_20413" x="-507.711" y="193.054" width="924.028" height="756.825" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="85.9684" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter11_f_11710_20413" x="-614.253" y="193.772" width="2650.34" height="1460.6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter11_f_11710_20413" x="-614.253" y="193.772" width="2650.34" height="1460.6" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="81.7748" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter12_f_11710_20413" x="1155.3" y="-124.759" width="648.704" height="452.589" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter12_f_11710_20413" x="1155.3" y="-124.759" width="648.704" height="452.589" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="35.6454" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter13_f_11710_20413" x="312.116" y="411.805" width="1664.88" height="621.546" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter13_f_11710_20413" x="312.116" y="411.805" width="1664.88" height="621.546" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="3.66938" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter14_f_11710_20413" x="924.792" y="388.825" width="1705.74" height="463.133" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter14_f_11710_20413" x="924.792" y="388.825" width="1705.74" height="463.133" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="3.66938" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<filter id="filter15_f_11710_20413" x="856.408" y="284.447" width="1752.22" height="636.689" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<filter id="filter15_f_11710_20413" x="856.408" y="284.447" width="1752.22" height="636.689" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="12.5807" result="effect1_foregroundBlur_11710_20413"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_11710_20413" x1="756" y1="0" x2="756" y2="762.413" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.259615" stop-color="#112674"/>
|
||||
<stop offset="1" stop-color="#3CA1EE" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3CA1EE" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_11710_20413" x1="786.004" y1="-13.7994" x2="86.8029" y2="577.525" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#54BDFF"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="white" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_11710_20413" x1="526.627" y1="253.906" x2="32.3322" y2="656.59" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.0104167" stop-color="#91B0ED"/>
|
||||
@@ -159,17 +159,17 @@
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_11710_20413" x1="1123.2" y1="471.463" x2="806.172" y2="1086.51" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3A4CF0"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="white" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_11710_20413" x1="1777.66" y1="396.164" x2="1380.85" y2="960.491" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3AF0C6"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="white" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_11710_20413" x1="1497.76" y1="544.384" x2="1261.11" y2="967.561" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.166667" stop-color="#60B5FF"/>
|
||||
<stop offset="0.541667" stop-color="#DBC0C6" stop-opacity="0.69"/>
|
||||
<stop offset="0.791667" stop-color="#C2DEFF" stop-opacity="0.62"/>
|
||||
<stop offset="1" stop-color="#9492FF" stop-opacity="0"/>
|
||||
<stop offset="0.541667" stop-color="#DBC0C6" stopOpacity="0.69"/>
|
||||
<stop offset="0.791667" stop-color="#C2DEFF" stopOpacity="0.62"/>
|
||||
<stop offset="1" stop-color="#9492FF" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_11710_20413">
|
||||
<rect width="1509.69" height="943.556" fill="white" transform="translate(1.15527)"/>
|
||||
|
||||
|
قبل العرض: | الارتفاع: | الحجم: 12 KiB بعد العرض: | الارتفاع: | الحجم: 12 KiB |
@@ -1 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stopColor="#08B962"></stop><stop offset="1" stopColor="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stopColor="#F94543"></stop><stop offset="1" stopColor="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stopColor="#FABC12"></stop><stop offset=".46" stopColor="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stopColor="#08B962"></stop><stop offset="1" stopColor="#08B962" stopOpacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stopColor="#F94543"></stop><stop offset="1" stopColor="#F94543" stopOpacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stopColor="#FABC12"></stop><stop offset=".46" stopColor="#FABC12" stopOpacity="0"></stop></linearGradient></defs></svg>
|
||||
|
قبل العرض: | الارتفاع: | الحجم: 2.8 KiB بعد العرض: | الارتفاع: | الحجم: 2.8 KiB |
@@ -13,7 +13,7 @@ export interface KeywordRow {
|
||||
status: "active" | "pause" | "Add-Keyword";
|
||||
}
|
||||
|
||||
export interface KeywordSection {
|
||||
export interface IKeywordSection {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
@@ -21,7 +21,7 @@ export interface KeywordRow {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export const keywordSections: KeywordSection[] = [
|
||||
export const keywordSections: IKeywordSection[] = [
|
||||
{
|
||||
id: "bofu",
|
||||
title: "Transactional, Immediate Business Impact, Highest Competition Opportunities (Bottom of Funnel - BoFu)",
|
||||
|
||||
@@ -78,22 +78,22 @@ function RankingsCard() {
|
||||
</g>
|
||||
<g filter="url(#filter2_f_12117_25665)">
|
||||
<path d="M0.565145 5.70498C0.254187 5.73711 0 5.98743 0 6.30005C0 6.61266 0.254187 6.86299 0.565145 6.89512C3.11491 7.1586 5.14145 9.18514 5.40493 11.7349C5.43706 12.0459 5.68739 12.3 6 12.3C6.31261 12.3 6.56294 12.0459 6.59507 11.7349C6.85855 9.18514 8.88509 7.1586 11.4349 6.89512C11.7458 6.86299 12 6.61266 12 6.30005C12 5.98743 11.7458 5.73711 11.4349 5.70498C8.88509 5.4415 6.85855 3.41496 6.59507 0.865194C6.56294 0.554236 6.31261 0.300049 6 0.300049C5.68739 0.300049 5.43706 0.554236 5.40493 0.865194C5.14145 3.41496 3.11491 5.4415 0.565145 5.70498Z" fill="#1F80FF" fill-opacity="0.01" />
|
||||
<path d="M0.565145 5.70498C0.254187 5.73711 0 5.98743 0 6.30005C0 6.61266 0.254187 6.86299 0.565145 6.89512C3.11491 7.1586 5.14145 9.18514 5.40493 11.7349C5.43706 12.0459 5.68739 12.3 6 12.3C6.31261 12.3 6.56294 12.0459 6.59507 11.7349C6.85855 9.18514 8.88509 7.1586 11.4349 6.89512C11.7458 6.86299 12 6.61266 12 6.30005C12 5.98743 11.7458 5.73711 11.4349 5.70498C8.88509 5.4415 6.85855 3.41496 6.59507 0.865194C6.56294 0.554236 6.31261 0.300049 6 0.300049C5.68739 0.300049 5.43706 0.554236 5.40493 0.865194C5.14145 3.41496 3.11491 5.4415 0.565145 5.70498Z" stroke="#58B0F7" stroke-width="0.905661" />
|
||||
<path d="M0.565145 5.70498C0.254187 5.73711 0 5.98743 0 6.30005C0 6.61266 0.254187 6.86299 0.565145 6.89512C3.11491 7.1586 5.14145 9.18514 5.40493 11.7349C5.43706 12.0459 5.68739 12.3 6 12.3C6.31261 12.3 6.56294 12.0459 6.59507 11.7349C6.85855 9.18514 8.88509 7.1586 11.4349 6.89512C11.7458 6.86299 12 6.61266 12 6.30005C12 5.98743 11.7458 5.73711 11.4349 5.70498C8.88509 5.4415 6.85855 3.41496 6.59507 0.865194C6.56294 0.554236 6.31261 0.300049 6 0.300049C5.68739 0.300049 5.43706 0.554236 5.40493 0.865194C5.14145 3.41496 3.11491 5.4415 0.565145 5.70498Z" stroke="#58B0F7" strokeWidth="0.905661" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_12117_25665" x="-0.226747" y="1.65845" width="12" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<filter id="filter0_f_12117_25665" x="-0.226747" y="1.65845" width="12" height="12" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="1.35849" result="effect1_foregroundBlur_12117_25665" />
|
||||
</filter>
|
||||
<filter id="filter1_f_12117_25665" x="1.97052" y="2.8017" width="14.0277" height="12.4652" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<filter id="filter1_f_12117_25665" x="1.97052" y="2.8017" width="14.0277" height="12.4652" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="1.35849" result="effect1_foregroundBlur_12117_25665" />
|
||||
</filter>
|
||||
<filter id="filter2_f_12117_25665" x="-1.81131" y="-1.51126" width="15.6226" height="15.6226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<filter id="filter2_f_12117_25665" x="-1.81131" y="-1.51126" width="15.6226" height="15.6226" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="0.679245" result="effect1_foregroundBlur_12117_25665" />
|
||||
</filter>
|
||||
|
||||
تم حذف اختلاف الملف لأن الملف كبير جداً
تحميل الاختلاف
@@ -6,11 +6,12 @@ import { ActionBadge } from "./shared/ActionBadge";
|
||||
|
||||
interface KeywordRowProps {
|
||||
keyword: KeywordRowType;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function KeywordRow({ keyword }: KeywordRowProps) {
|
||||
export function KeywordRow({ keyword, isLoading = false }: KeywordRowProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4 justify-items-cente px-6 pr-[52px] py-3 border-t border-grey-300 min-h-[54px] hover:bg-gray-100 transition-colors">
|
||||
<div className={`grid grid-cols-12 gap-4 justify-items-cente px-6 pr-[52px] py-3 border-t border-grey-300 min-h-[54px] transition-colors ${isLoading ? 'opacity-50 bg-gray-50' : 'hover:bg-gray-100'}`}>
|
||||
|
||||
<div className="flex items-center gap-1 mr-20 col-span-2" >
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0">
|
||||
@@ -20,15 +21,27 @@ export function KeywordRow({ keyword }: KeywordRowProps) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="text-sm font-semibold text-grey-900 leading-5">{keyword.searchVolume}</span>
|
||||
{isLoading ? (
|
||||
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin" />
|
||||
) : (
|
||||
<span className="text-sm font-semibold text-grey-900 leading-5">{keyword.searchVolume}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<MetricBadge type="google" value={keyword.googlePos} />
|
||||
{isLoading ? (
|
||||
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin" />
|
||||
) : (
|
||||
<MetricBadge type="google" value={keyword.googlePos} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{keyword.mapsPos && <MetricBadge type="maps" value={keyword.mapsPos} />}
|
||||
{isLoading ? (
|
||||
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin" />
|
||||
) : keyword.mapsPos ? (
|
||||
<MetricBadge type="maps" value={keyword.mapsPos} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import { type KeywordSection as KeywordSectionType } from "../assets/keyword-data";
|
||||
import { type IKeywordSection as KeywordSectionType } from "../assets/keyword-data";
|
||||
import { KeywordRow } from "./KeywordRow";
|
||||
import MetricCard from "./shared/MetricCard";
|
||||
import MetricTooltip from "./shared/MetricTooltip";
|
||||
|
||||
interface KeywordSectionProps {
|
||||
section: KeywordSectionType;
|
||||
loadingKeywords?: Set<string>;
|
||||
}
|
||||
|
||||
export function KeywordSection({ section }: KeywordSectionProps) {
|
||||
export function KeywordSection({ section, loadingKeywords = new Set() }: KeywordSectionProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(section.expanded);
|
||||
|
||||
return (
|
||||
@@ -20,11 +21,11 @@ export function KeywordSection({ section }: KeywordSectionProps) {
|
||||
<div className="w-6 h-6 flex items-center justify-center shrink-0">
|
||||
{isExpanded ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="8" viewBox="0 0 14 8" fill="none">
|
||||
<path d="M1 1L7 7L13 0.999999" stroke="#65677D" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M1 1L7 7L13 0.999999" stroke="#65677D" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
||||
<path d="M1 1L7 7L1 13" stroke="#65677D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M1 1L7 7L1 13" stroke="#65677D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
@@ -75,7 +76,11 @@ export function KeywordSection({ section }: KeywordSectionProps) {
|
||||
|
||||
<div className="bg-grey-50">
|
||||
{section.keywords.map((keyword) => (
|
||||
<KeywordRow key={keyword.id} keyword={keyword} />
|
||||
<KeywordRow
|
||||
key={keyword.id}
|
||||
keyword={keyword}
|
||||
isLoading={loadingKeywords.has(keyword.keyword)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ function ProgressMetrics() {
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_11446_9597" x1="59.6985" y1="14.9105" x2="59.6985" y2="65.0109" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#DF8D2D"/>
|
||||
<stop offset="1" stopColor="#794D18" stop-opacity="0"/>
|
||||
<stop offset="1" stopColor="#794D18" stopOpacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_11446_9597" x1="3.31445" y1="39.9607" x2="142.824" y2="39.942" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#2F82F8"/>
|
||||
|
||||
@@ -8,9 +8,9 @@ export function ActionBadge({ type }: ActionBadgeProps) {
|
||||
|
||||
if (type === "Add-Keyword") {
|
||||
return (
|
||||
<button className="flex items-center justify-center gap-0.5 px-1.5 py-0.5 rounded-md border border-grey-300 bg-white shadow-[0_1px_1px_0_rgba(5,19,65,0.07),0_1px_4px_-2px_rgba(24,39,75,0.02),0_0_2px_0_#E0E0E0] h-7 hover:bg-grey-50 transition-colors shrink-0 w-1/11">
|
||||
<Plus className="w-4 h-4 text-grey-800" strokeWidth={1.5} />
|
||||
<span className="text-xs font-semibold text-grey-900 leading-[18px]">Add Keyword</span>
|
||||
<button className="flex items-center justify-center gap-0.5 px-1.5 py-0.5 rounded-md border border-gray-300 bg-white shadow-[0_1px_1px_0_rgba(5,19,65,0.07),0_1px_4px_-2px_rgba(24,39,75,0.02),0_0_2px_0_#E0E0E0] h-7 hover:bg-gray-50 transition-colors shrink-0 w-1/11">
|
||||
<Plus className="w-4 h-4 text-gray-800" strokeWidth={1.5} />
|
||||
<span className="text-xs font-semibold text-gray-900 leading-[18px] whitespace-nowrap">Add Keyword</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
14
src/components/shared/GetUser.ts
Normal file
14
src/components/shared/GetUser.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const GetUser = () => {
|
||||
let user = null;
|
||||
const userRaw = localStorage.getItem('sp_user');
|
||||
if (userRaw) {
|
||||
try {
|
||||
const userObj = JSON.parse(userRaw);
|
||||
user = userObj?.email || null;
|
||||
return user
|
||||
} catch (e) {
|
||||
user = null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ const Header = () => {
|
||||
aria-label="Close overlay"
|
||||
/>
|
||||
<div className="fixed inset-0 flex items-center justify-center z-[60] p-4 pointer-events-none">
|
||||
<div className="pointer-events-auto overflow-y-auto max-h-full">
|
||||
<div className="pointer-events-auto overflow-y-auto max-w-[700px] lg:">
|
||||
<KeywordConfirmationModal onClose={() => setShowOverlay(false)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import MetricCard from "./MetricCard"
|
||||
import UrlDropdown from "./UrlDropdown"
|
||||
import { useAppSelector } from "../../store/hooks"
|
||||
|
||||
const HeaderPage = ({ title, buttonShow = false }: { title: string, buttonShow?: boolean }) => {
|
||||
const url = useAppSelector((state) => state.url.url)
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row items-baseline gap-4 my-0 md:my-4 justify-between">
|
||||
<div className="flex items-baseline gap-4 flex-wrap">
|
||||
@@ -19,8 +21,8 @@ const HeaderPage = ({ title, buttonShow = false }: { title: string, buttonShow?:
|
||||
<circle cx="2" cy="2" r="2" fill="#E9EAF1" />
|
||||
</svg>
|
||||
|
||||
<UrlDropdown />
|
||||
|
||||
<UrlDropdown url={url ? [url] : undefined} />
|
||||
|
||||
{/* Second SVG dot */}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -44,7 +46,7 @@ const HeaderPage = ({ title, buttonShow = false }: { title: string, buttonShow?:
|
||||
buttonShow &&
|
||||
<button className='bg-[#fff] border px-2 sm:px-3 py-2 rounded-lg text-sm sm:text-md font-medium transition-colors flex items-center gap-2'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path d="M2.25 12.5735V14.1618C2.25 14.583 2.42779 14.987 2.74426 15.2848C3.06072 15.5827 3.48995 15.75 3.9375 15.75H14.0625C14.5101 15.75 14.9393 15.5827 15.2557 15.2848C15.5722 14.987 15.75 14.583 15.75 14.1618V12.5735M4.78125 6.22059L9 2.25M9 2.25L13.2187 6.22059M9 2.25V11.7794" stroke="#65677D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M2.25 12.5735V14.1618C2.25 14.583 2.42779 14.987 2.74426 15.2848C3.06072 15.5827 3.48995 15.75 3.9375 15.75H14.0625C14.5101 15.75 14.9393 15.5827 15.2557 15.2848C15.5722 14.987 15.75 14.583 15.75 14.1618V12.5735M4.78125 6.22059L9 2.25M9 2.25L13.2187 6.22059M9 2.25V11.7794" stroke="#65677D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
|
||||
<span>Export to Slides</span>
|
||||
|
||||
@@ -8,9 +8,9 @@ const urlOptions = [
|
||||
"http://onekeyword.com"
|
||||
];
|
||||
|
||||
export default function UrlDropdown() {
|
||||
export default function UrlDropdown({ url }: { url?: string[] }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(urlOptions[0]);
|
||||
const [selected, setSelected] = useState(url?.[0]);
|
||||
|
||||
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
||||
const handleSelect = (url: string) => {
|
||||
@@ -42,7 +42,7 @@ export default function UrlDropdown() {
|
||||
{/* Dropdown list */}
|
||||
{isOpen && (
|
||||
<div className="absolute mt-1 bg-white border border-gray-200 rounded shadow max-h-60 overflow-y-auto z-10 w-[110%]">
|
||||
{urlOptions.map((url, index) => (
|
||||
{url?.map((url, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => handleSelect(url)}
|
||||
|
||||
10
src/main.tsx
10
src/main.tsx
@@ -12,6 +12,8 @@ import { setContext } from '@apollo/client/link/context'
|
||||
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
|
||||
import { createClient } from 'graphql-ws'
|
||||
import { getMainDefinition } from '@apollo/client/utilities'
|
||||
import { Provider } from 'react-redux'
|
||||
import { store } from './store/store'
|
||||
|
||||
// Initialize Nhost client
|
||||
const nhost = new NhostClient({
|
||||
@@ -98,9 +100,11 @@ const client = new ApolloClient({
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<NhostProvider nhost={nhost}>
|
||||
<StrictMode>
|
||||
<ApolloProvider client={client}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
<Provider store={store}>
|
||||
<ApolloProvider client={client}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
</NhostProvider>,
|
||||
)
|
||||
@@ -17,9 +17,6 @@ import MetricTooltip from "../components/shared/MetricTooltip";
|
||||
|
||||
const Analysis = () => {
|
||||
|
||||
{/* <BecomeTheSource /> */ }
|
||||
// <SourcesChart />
|
||||
|
||||
const healthcareSegments = [
|
||||
{ percentage: 40, color: "#4C60E5" },
|
||||
{ percentage: 20, color: "#9CCCFa" },
|
||||
@@ -45,11 +42,31 @@ const Analysis = () => {
|
||||
];
|
||||
|
||||
const sourceItems = [
|
||||
{ label: "30% Branded Aesthetics Social Signals", color: "#4C60E5", icons: ["🎯", "📌", "🎨"] },
|
||||
{ label: "30% Local + Medical-Cosmetic Citations", color: "#4285F4", icons: ["🏥", "📍", "⚕️"] },
|
||||
{ label: "20% Web 2.0 Microblogging Sites", color: "#34A853", icons: ["✍️", "💬", "📱"] },
|
||||
{ label: "20% Multimedia Content & Beauty Visuals", color: "#FBBC05", icons: ["📹", "📷", "🎬"] },
|
||||
{ label: "10% Community and UGC Signals", color: "#EA4335", icons: ["👥", "💭", "⭐"] },
|
||||
{
|
||||
label: "30% Branded Aesthetics Social Signals",
|
||||
color: "#4C60E5",
|
||||
images: ["src/assets/icons/behance.png", "src/assets/icons/pintrest.png", "src/assets/icons/icon-2.png"],
|
||||
},
|
||||
{
|
||||
label: "30% Local + Medical-Cosmetic Citations",
|
||||
color: "#4285F4",
|
||||
images: ["src/assets/icons/icon-3.png", "src/assets/icons/icon-4.png", "src/assets/icons/icon-5.png"],
|
||||
},
|
||||
{
|
||||
label: "20% Web 2.0 Microblogging Sites",
|
||||
color: "#34A853",
|
||||
images: ["src/assets/icons/icon-6.png", "src/assets/icons/icon-7.png", "src/assets/icons/icon-8.png"],
|
||||
},
|
||||
{
|
||||
label: "20% Multimedia Content & Beauty Visuals",
|
||||
color: "#FBBC05",
|
||||
images: ["src/assets/icons/icon-9.png", "src/assets/icons/icon-10.png", "src/assets/icons/icon-11.png"],
|
||||
},
|
||||
{
|
||||
label: "10% Community and UGC Signals",
|
||||
color: "#EA4335",
|
||||
images: ["src/assets/icons/icon-12.png", "src/assets/icons/icon-13.png", "src/assets/icons/icon-14.png"],
|
||||
},
|
||||
];
|
||||
|
||||
interface TableRow {
|
||||
@@ -115,324 +132,335 @@ const Analysis = () => {
|
||||
<Header />
|
||||
|
||||
<ContainerPage>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="mb-2 flex justify-between items-center">
|
||||
<h1 className="text-xl font-bold text-gray-950 mb-1">Analysis of Visibility</h1>
|
||||
<div className="flex items-center gap-[2.3rem] w-[61%] text-xs font-semibold text-gray-700 mt-1">
|
||||
<div className="w-22 flex items-center gap-2">
|
||||
<span className="w-full">
|
||||
AIO Health
|
||||
</span>
|
||||
<MetricTooltip tooltipText="AIO Health" widthfit={true} />
|
||||
</div>
|
||||
<div className="w-20 flex items-center gap-2">Backlinks
|
||||
<MetricTooltip tooltipText="Backlinks" widthfit={true} />
|
||||
</div>
|
||||
<div className="w-14 text-[#65677D]">G Pos #</div>
|
||||
<div className="w-10 text-[#65677D]">Maps</div>
|
||||
<div className="w-20 text-[#65677D]">Ai Overview</div>
|
||||
<div className="w-16 text-[#65677D]">Chat GPT</div>
|
||||
<div className="w-16 text-[#65677D]">Gemini</div>
|
||||
<div className="w-16 text-[#65677D]">Perplexity</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border border-gray-300 rounded-lg bg-white p-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-gray-950">BH Medical Spa of Beverly Hills</h2>
|
||||
<div className="flex items-center gap-4 mt-1 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-semibold text-[#17171B]">Sector:</span>
|
||||
<span className="text-[#65677D] font-normal">Healthcare</span>
|
||||
<div className="max-w-[1360px] mx-auto inter-font">
|
||||
<div className="mb-[13px] pt-[18px]">
|
||||
<div className="flex items-center justify-between mb-[9px]">
|
||||
<h1 className="text-xl font-bold text-gray-950">Analysis of Visibility</h1>
|
||||
<div className="flex items-center gap-[2.3rem] w-[73%] text-xs font-semibold text-gray-700 mt-1">
|
||||
<div className="flex items-center gap-[2px] w-22">
|
||||
<span className="w-full">
|
||||
AIO Health
|
||||
</span>
|
||||
<MetricTooltip tooltipText="AIO Health" widthfit={true} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-semibold text-[#17171B]">Industry:</span>
|
||||
<span className="text-[#65677D] font-normal">Medical Spa</span>
|
||||
<div className="flex items-center w-20 ml-[-11px] gap-2">Backlinks
|
||||
<MetricTooltip tooltipText="Backlinks" widthfit={true} />
|
||||
</div>
|
||||
<div className="w-14 ml-8 text-[#65677D]">G Pos #</div>
|
||||
<div className="w-10 text-[#65677D]">Maps</div>
|
||||
<div className="w-20 ml-[14px] text-[#65677D]">Ai Overview</div>
|
||||
<div className="w-16 text-[#65677D]">Chat GPT</div>
|
||||
<div className="w-16 ml-2 text-[#65677D]">Gemini</div>
|
||||
<div className="w-16 ml-[17px] text-[#65677D]">Perplexity</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-[2.3rem] w-[60%]">
|
||||
<div className="flex items-center gap-3">
|
||||
<svg width="20" height="20" viewBox="0 0 23 24" className="flex-shrink-0">
|
||||
<circle opacity="0.8" cx="11.5" cy="12" r="9.55" stroke="#E9EAF1" strokeWidth="3.32" fill="none" />
|
||||
<path d="M11.5518 2.44824C13.8878 2.44824 16.1428 3.30431 17.8904 4.85452C19.6379 6.40473 20.7568 8.54161 21.0354 10.861" stroke="#4C60E5" strokeWidth="3.32" strokeLinecap="round" fill="none" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">24%</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<svg width="20" height="20" viewBox="0 0 23 24" className="flex-shrink-0">
|
||||
<circle opacity="0.8" cx="11.5" cy="12" r="9.55" stroke="#E9EAF1" strokeWidth="3.32" fill="none" />
|
||||
<path d="M11.5518 2.44824C13.8878 2.44824 16.1428 3.30431 17.8904 4.85452C19.6379 6.40473 20.7568 8.54161 21.0354 10.861" stroke="#4C60E5" strokeWidth="3.32" strokeLinecap="round" fill="none" />
|
||||
</svg>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold text-gray-900">14%</span>
|
||||
<span className="text-[11px] font-semibold text-gray-700">50/370 links</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-2.5 py-1.5 rounded-3xl border border-gray-300 bg-white">
|
||||
<svg width="15" height="15" viewBox="0 0 15 16">
|
||||
<path d="M13.9853 8.15431C13.9853 7.67081 13.9419 7.2059 13.8613 6.75958H7.43933V9.40027H11.109C10.9478 10.2495 10.4643 10.9686 9.73908 11.4521V13.1691H11.9521C13.2414 11.979 13.9853 10.2309 13.9853 8.15431Z" fill="#4285F4" />
|
||||
<path d="M7.43878 14.8186C9.27982 14.8186 10.8233 14.2111 11.9515 13.1697L9.73853 11.4527C9.13105 11.8618 8.3562 12.1097 7.43878 12.1097C5.66592 12.1097 4.15962 10.9134 3.62032 9.3017H1.35156V11.0622C2.47354 13.2875 4.7733 14.8186 7.43878 14.8186Z" fill="#34A853" />
|
||||
<path d="M3.62113 9.29494C3.48475 8.88582 3.40417 8.45191 3.40417 7.99939C3.40417 7.54688 3.48475 7.11297 3.62113 6.70385V4.94339H1.35237C0.887459 5.86081 0.620911 6.89601 0.620911 7.99939C0.620911 9.10278 0.887459 10.138 1.35237 11.0554L3.11902 9.67927L3.62113 9.29494Z" fill="#FBBC05" />
|
||||
<path d="M7.43878 3.89567C8.44298 3.89567 9.33561 4.24281 10.0485 4.91228L12.0011 2.95966C10.8171 1.85627 9.27982 1.1806 7.43878 1.1806C4.7733 1.1806 2.47354 2.71171 1.35156 4.94327L3.62032 6.70373C4.15962 5.09204 5.66592 3.89567 7.43878 3.89567Z" fill="#EA4335" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">5</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-2.5 py-1.5 rounded-3xl border border-gray-300 bg-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="16" viewBox="0 0 11 16" fill="none">
|
||||
<path d="M2.80688 11.5217C3.25814 12.086 3.71773 12.7957 3.95824 13.2243C4.25114 13.7815 4.37259 14.1578 4.59047 14.8317C4.71787 15.2008 4.83932 15.3127 5.09412 15.3127C5.37273 15.3127 5.50013 15.1246 5.59776 14.8317C5.80136 14.1994 5.95853 13.7184 6.20737 13.26C7.16703 11.4502 8.73155 10.1643 9.61262 8.40216C9.61262 8.40216 10.1925 7.32582 10.1925 5.81846C10.1925 4.41349 9.62096 3.43716 9.62096 3.43716L2.81046 11.5336L2.80688 11.5217Z" fill="#34A853" />
|
||||
<path d="M0.49625 8.21276C1.04514 9.46294 2.09172 10.5583 2.80611 11.5228L6.59236 7.03402C6.59236 7.03402 6.05776 7.73412 5.09215 7.73412C4.0158 7.73412 3.13949 6.87686 3.13949 5.79337C3.13949 5.04802 3.5836 4.53128 3.5836 4.53128C0.797483 4.94563 0.952267 5.62072 0.487915 8.21038L0.49625 8.21276Z" fill="#FBBC04" />
|
||||
<path d="M6.64104 0.922089C7.90313 1.3281 8.97471 2.18417 9.61766 3.43435L6.59342 7.04202C6.59342 7.04202 7.03753 6.52289 7.03753 5.77993C7.03753 4.67382 6.10406 3.83917 5.09677 3.83917C4.14068 3.83917 3.59656 4.53094 3.59656 4.53094C3.82873 4.00229 6.22789 1.10188 6.64462 0.923279L6.64104 0.922089Z" fill="#4285F4" />
|
||||
<path d="M1.18726 2.50895C1.93975 1.61358 3.25898 0.687256 5.08067 0.687256C5.96056 0.687256 6.62851 0.920623 6.62851 0.920623L3.59236 4.52828C3.38757 4.4172 1.38967 2.86138 1.18726 2.50418V2.50895Z" fill="#1A73E8" />
|
||||
<path d="M0.496744 8.21327C0.496744 8.21327 0.000244141 7.22741 0.000244141 5.80816C0.000244141 4.46273 0.52651 3.28399 1.19089 2.52197L3.596 4.54607L0.500316 8.21327H0.496744Z" fill="#EA4335" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">8</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="text-xs text-gray-700 opacity-30 border-2 border-gray-200 px-2 py-1 rounded-full flex items-center justify-between gap-2">
|
||||
<img src={Gemini} alt="G" className="w-4 h-4" />
|
||||
Invisible
|
||||
</div>
|
||||
<div className="text-xs text-gray-700 opacity-30 border-2 border-gray-200 px-2 py-1 rounded-full flex items-center justify-between gap-2">
|
||||
<img src={ChatGPT} alt="ChatGPT" className="w-4 h-4" />
|
||||
Invisible</div>
|
||||
<div className="text-xs text-gray-700 opacity-30 border-2 border-gray-200 px-2 py-1 rounded-full flex items-center justify-between gap-2">
|
||||
<img src={Gemini} alt="Gemini" className="w-4 h-4" />Invisible</div>
|
||||
<div className="text-xs text-gray-700 opacity-30 border-2 border-gray-200 px-2 py-1 rounded-full flex items-center justify-between gap-2">
|
||||
<img src={Preplextiy} alt="Perplexity" className="w-4 h-4" />
|
||||
Invisible</div>
|
||||
</div>
|
||||
|
||||
<button className="flex items-center gap-1 px-1.5 py-1 rounded-md border border-gray-300 bg-white shadow-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16" fill="none">
|
||||
<path d="M6.59226 2H5.69226C3.75926 2 2.19226 3.567 2.19226 5.5V10.5C2.19226 12.433 3.75926 14 5.69226 14H10.6923C12.6253 14 14.1923 12.4333 14.1923 10.5003C14.1923 10.2415 14.1923 9.95005 14.1923 9.6" stroke="#65677D" stroke-width="1.5" stroke-linecap="round" />
|
||||
<path d="M10.1923 2H14.1923M14.1923 2V6M14.1923 2L8.19128 8" stroke="#65677D" stroke-width="1.5" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<span className="text-xs font-semibold text-gray-900">Share</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5 mb-4">
|
||||
|
||||
|
||||
|
||||
{showDetails ?
|
||||
<div className="flex flex-col gap-5 rounded-xl bg-white border border-gray-300 shadow-sm overflow-hidden justify-between">
|
||||
<div className="flex items-center gap-2 px-4 py-3 border-b border-gray-300 h-[60px]">
|
||||
<h2 className="text-base font-semibold text-gray-950">Ai Frequently Cited Sources in Healthcare</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between h-full gap-8 px-6 pb-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<DonutChart segments={healthcareSegments} size={200} strokeWidth={53} />
|
||||
|
||||
<div className="flex flex-col gap-1.5 flex-1">
|
||||
{healthcareItems.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center gap-2.5 px-2 py-1.5 rounded-md border hover:border-[#C9D7FB] hover:bg-[#ECF1FD] cursor-pointer ${item.highlighted ? "border-[#C9D7FB] bg-[#ECF1FD]" : "border-gray-300"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedItem(item);
|
||||
setShowDetails(false);
|
||||
}}
|
||||
>
|
||||
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: item.color }} />
|
||||
<div className="flex items-center gap-0.5 flex-1">
|
||||
<span className="text-sm">
|
||||
<span className="font-bold text-gray-900">{item.label.split(" ")[0]}</span>
|
||||
<span className="text-gray-900 font-medium"> {item.label.split(" ").slice(1).join(" ")}</span>
|
||||
</span>
|
||||
{item.subtitle && (
|
||||
<span className="text-sm text-gray-700 ml-1">{item.subtitle}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-3 py-3 rounded-xl bg-[#ECF1FD]">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="18" viewBox="0 0 14 18" fill="none">
|
||||
<path d="M7.05074 3.62323C5.68916 3.62323 4.49145 4.39338 3.86719 5.52409" stroke="#17171B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M4.27642 14.6968C4.55366 14.6212 4.83089 14.5456 5.10813 14.47C9.24146 13.2854 9.03984 13.3862 9.11545 13.2098C9.72033 11.1936 10.1236 11.748 11.2073 10.2106C11.7114 9.47974 12.0894 8.64804 12.2407 7.76592C12.3163 7.41308 12.3415 7.03503 12.3415 6.65698C12.3415 3.50657 9.79594 0.961044 6.67073 0.961044C3.54553 0.961044 1 3.50657 1 6.63178C1 7.51389 1.17642 8.42121 1.55447 9.22771C1.83171 9.83259 2.23496 10.3871 2.68862 10.8659C3.08885 11.2852 3.58998 11.6613 3.90862 12.1576" stroke="#17171B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M5.28516 17.039L8.81361 16.0056" stroke="#17171B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-950">How Ai and LLMs Choose Their Sources</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-900 leading-[18px]">
|
||||
Our analysis reveals distinct patterns in how leading AIs source and prioritize industry information. For example, ChatGPT leans heavily on user-generated content, while Google's AI Overviews favors authoritative tech review sites. Across the board, backlink quality plays a crucial role, with...{" "}
|
||||
<span className="text-[#4C60E5] underline cursor-pointer">read more</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
(
|
||||
<div className="w-full bg-white rounded-xl shadow-elevation flex flex-col border border-gray-300">
|
||||
<div className="flex items-center gap-4 px-4 py-2 ">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="flex items-center justify-center gap-1.5 w-7 h-6 rounded-md border border-gray-300 bg-transparent hover:bg-gray-200 transition-colors"
|
||||
onClick={() => setShowDetails(true)}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 text-gray-800" strokeWidth={1.275} />
|
||||
</button>
|
||||
<h1 className="text-sm font-semibold">
|
||||
<span className="font-normal text-gray-800">Consumer Health Giants... </span>
|
||||
<span className="font-bold text-[#2B2D3B]">Good Lick With That </span>
|
||||
<span className="font-normal text-[#2B2D3B]">Tier</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full mx-auto border-t border-[#d0d7d8] shadow-xl ">
|
||||
<div className="h-1.5 bg-gradient-to-b from-[#0A2527]/8 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1 px-2 pb-2">
|
||||
{/* Table Header */}
|
||||
<div className="hidden md:flex items-center px-6 py-2 border-b border-gray-300 text-body-s font-semibold text-gray-800 gap-3">
|
||||
<div className="flex items-center font-semibold gap-2 w-[160px] flex-shrink-0 text-[#65677D]">
|
||||
<span>Source</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[60px] flex-shrink-0 justify-center text-[#65677D]">
|
||||
<span>AIQ</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[233px] flex-shrink-0 text-[#65677D]">
|
||||
<span>How to Get Cited</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[180px] flex-shrink-0 text-[#65677D]">
|
||||
<span>Difficulty</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<div className="overflow-y-auto h-full px-2">
|
||||
{tableData.map((row) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="flex flex-col md:flex-row items-start md:items-start border-b border-gray-200 bg-white px-6 pt-3 pb-6 hover:bg-gray-50 transition h-[30%] gap-3"
|
||||
>
|
||||
{/* Source */}
|
||||
<div className="flex items-center gap-2 w-[160px] flex-shrink-0">
|
||||
<div className="w-5 h-5 rounded-sm overflow-hidde">
|
||||
<img
|
||||
src={row.source.icon}
|
||||
alt={row.source.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
href={row.source.link}
|
||||
className="text-sm text-[#4C60E5] underline hover:no-underline truncatse"
|
||||
>
|
||||
{row.source.name}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* AIQ */}
|
||||
<div className="w-[60px] text-sm text-[#65677D] flex-shrink-0">
|
||||
{row.aiq}
|
||||
</div>
|
||||
|
||||
{/* How to Get Cited */}
|
||||
<div className="w-[233px] text-sm text-[#65677D] leading-snug flex-shrink-0">
|
||||
{row.howToGetCited}
|
||||
</div>
|
||||
|
||||
{/* Difficulty */}
|
||||
<div className="w-[180px] text-sm text-[#65677D] leading-snug flex-shrink-0">
|
||||
{row.difficulty}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-center items-center py-3.5 px-2 rounded-lg bg-[#F2F3F7]">
|
||||
<p className="text italic text-[#65677D] text-center max-w-[800px]">
|
||||
"Mass SEO factories disguised as medical authorities."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex flex-col gap-5 rounded-xl bg-white border border-gray-300 shadow-sm overflow-hidden">
|
||||
<div className="flex flex-col gap-7 px-4 py-3 border-b border-gray-300 h-[60px]">
|
||||
<div className="flex items-center justify-between px-4 py-2.5 bg-white border border-gray-300 rounded-lg">
|
||||
<div>
|
||||
<h2 className="text-base font-bold text-gray-950">Become the Source</h2>
|
||||
<p className="text-xs text-gray-700">
|
||||
Custom Authority Beacons & Backlink Strategy, Tailored for <span className="font-bold text-gray-950">Medical Spas</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 px-6 pb-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<DonutChart segments={sourceSegments} size={200} strokeWidth={51.76} />
|
||||
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
{sourceItems.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-1">
|
||||
<div className="w-[10px] h-[10px] rounded-full flex-shrink-0 mt-[10px]" style={{ backgroundColor: item.color }} />
|
||||
<div className="flex flex-col gap-0 flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="text-sm font-bold text-gray-900">{item.label.split(" ")[0]}</span>
|
||||
<MetricTooltip tooltipText={item.label.split(" ").slice(1).join(" ")} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{item.icons.map((icon, i) => (
|
||||
<div key={i} className="w-[30px] h-[30px] rounded-md border border-gray-300 bg-white flex items-center justify-center text-md">
|
||||
{icon}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">{item.label.split(" ").slice(1).join(" ")}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<h2 className="text-sm font-semibold text-gray-950">BH Medical Spa of Beverly Hills</h2>
|
||||
<div className="flex items-center gap-4 mt-1 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-semibold text-[#17171B]">Sector:</span>
|
||||
<span className="text-[#65677D] font-normal">Healthcare</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-semibold text-[#17171B]">Industry:</span>
|
||||
<span className="text-[#65677D] font-normal">Medical Spa</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<button className="w-[55%] px-2 py-6 rounded-full bg-white border-4 border-transparent bg-gradient-to-r from-blue-500 via-red-500 via-orange-400 via-yellow-400 to-green-500 bg-clip-padding relative group mx-auto hover:bg-[#ECF1FD] transition-colors">
|
||||
<div className="absolute inset-0 bg-white rounded-full m-[3px] flex items-center justify-center gap-3 hover:bg-[#ECF1FD] transition-colors">
|
||||
<div className="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center">
|
||||
<Zap className="w-4 h-4 text-white fill-white" />
|
||||
</div>
|
||||
<span className="text-md font-semibold text-gray-900">
|
||||
Add your Keywords and Become the Source
|
||||
</span>
|
||||
<div className="flex items-center gap-[2.3rem] w-[73%]">
|
||||
<div className="flex items-center gap-3 -ml-[9px] -mt-[5px]">
|
||||
<svg width="20" height="20" viewBox="0 0 23 24" className="flex-shrink-0">
|
||||
<circle opacity="0.8" cx="11.5" cy="12" r="9.55" stroke="#E9EAF1" strokeWidth="3.32" fill="none" />
|
||||
<path d="M11.5518 2.44824C13.8878 2.44824 16.1428 3.30431 17.8904 4.85452C19.6379 6.40473 20.7568 8.54161 21.0354 10.861" stroke="#4C60E5" strokeWidth="3.32" strokeLinecap="round" fill="none" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">24%</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 ml-[9px] -mt-[4px]">
|
||||
<svg width="20" height="20" viewBox="0 0 23 24" className="flex-shrink-0">
|
||||
<circle opacity="0.8" cx="11.5" cy="12" r="9.55" stroke="#E9EAF1" strokeWidth="3.32" fill="none" />
|
||||
<path d="M11.5518 2.44824C13.8878 2.44824 16.1428 3.30431 17.8904 4.85452C19.6379 6.40473 20.7568 8.54161 21.0354 10.861" stroke="#4C60E5" strokeWidth="3.32" strokeLinecap="round" fill="none" />
|
||||
</svg>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold text-gray-900">14%</span>
|
||||
<span className="text-[11px] font-semibold text-gray-700">50/370 links</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-2.5 py-1.5 ml-2 rounded-3xl border border-gray-300 bg-white">
|
||||
<svg width="15" height="15" viewBox="0 0 15 16">
|
||||
<path d="M13.9853 8.15431C13.9853 7.67081 13.9419 7.2059 13.8613 6.75958H7.43933V9.40027H11.109C10.9478 10.2495 10.4643 10.9686 9.73908 11.4521V13.1691H11.9521C13.2414 11.979 13.9853 10.2309 13.9853 8.15431Z" fill="#4285F4" />
|
||||
<path d="M7.43878 14.8186C9.27982 14.8186 10.8233 14.2111 11.9515 13.1697L9.73853 11.4527C9.13105 11.8618 8.3562 12.1097 7.43878 12.1097C5.66592 12.1097 4.15962 10.9134 3.62032 9.3017H1.35156V11.0622C2.47354 13.2875 4.7733 14.8186 7.43878 14.8186Z" fill="#34A853" />
|
||||
<path d="M3.62113 9.29494C3.48475 8.88582 3.40417 8.45191 3.40417 7.99939C3.40417 7.54688 3.48475 7.11297 3.62113 6.70385V4.94339H1.35237C0.887459 5.86081 0.620911 6.89601 0.620911 7.99939C0.620911 9.10278 0.887459 10.138 1.35237 11.0554L3.11902 9.67927L3.62113 9.29494Z" fill="#FBBC05" />
|
||||
<path d="M7.43878 3.89567C8.44298 3.89567 9.33561 4.24281 10.0485 4.91228L12.0011 2.95966C10.8171 1.85627 9.27982 1.1806 7.43878 1.1806C4.7733 1.1806 2.47354 2.71171 1.35156 4.94327L3.62032 6.70373C4.15962 5.09204 5.66592 3.89567 7.43878 3.89567Z" fill="#EA4335" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">5</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-2.5 py-1.5 rounded-3xl border border-gray-300 bg-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="16" viewBox="0 0 11 16" fill="none">
|
||||
<path d="M2.80688 11.5217C3.25814 12.086 3.71773 12.7957 3.95824 13.2243C4.25114 13.7815 4.37259 14.1578 4.59047 14.8317C4.71787 15.2008 4.83932 15.3127 5.09412 15.3127C5.37273 15.3127 5.50013 15.1246 5.59776 14.8317C5.80136 14.1994 5.95853 13.7184 6.20737 13.26C7.16703 11.4502 8.73155 10.1643 9.61262 8.40216C9.61262 8.40216 10.1925 7.32582 10.1925 5.81846C10.1925 4.41349 9.62096 3.43716 9.62096 3.43716L2.81046 11.5336L2.80688 11.5217Z" fill="#34A853" />
|
||||
<path d="M0.49625 8.21276C1.04514 9.46294 2.09172 10.5583 2.80611 11.5228L6.59236 7.03402C6.59236 7.03402 6.05776 7.73412 5.09215 7.73412C4.0158 7.73412 3.13949 6.87686 3.13949 5.79337C3.13949 5.04802 3.5836 4.53128 3.5836 4.53128C0.797483 4.94563 0.952267 5.62072 0.487915 8.21038L0.49625 8.21276Z" fill="#FBBC04" />
|
||||
<path d="M6.64104 0.922089C7.90313 1.3281 8.97471 2.18417 9.61766 3.43435L6.59342 7.04202C6.59342 7.04202 7.03753 6.52289 7.03753 5.77993C7.03753 4.67382 6.10406 3.83917 5.09677 3.83917C4.14068 3.83917 3.59656 4.53094 3.59656 4.53094C3.82873 4.00229 6.22789 1.10188 6.64462 0.923279L6.64104 0.922089Z" fill="#4285F4" />
|
||||
<path d="M1.18726 2.50895C1.93975 1.61358 3.25898 0.687256 5.08067 0.687256C5.96056 0.687256 6.62851 0.920623 6.62851 0.920623L3.59236 4.52828C3.38757 4.4172 1.38967 2.86138 1.18726 2.50418V2.50895Z" fill="#1A73E8" />
|
||||
<path d="M0.496744 8.21327C0.496744 8.21327 0.000244141 7.22741 0.000244141 5.80816C0.000244141 4.46273 0.52651 3.28399 1.19089 2.52197L3.596 4.54607L0.500316 8.21327H0.496744Z" fill="#EA4335" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-900">8</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-5 ml-2.5 -mt-[7px]">
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-[5px] text-xs text-gray-700 border-2 border-gray-200 rounded-full opacity-30">
|
||||
<img src={Gemini} alt="G" className="w-[17px] h-[17px]" />
|
||||
Invisible
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-[5px] text-xs text-gray-700 border-2 border-gray-200 rounded-full opacity-30">
|
||||
<img src={ChatGPT} alt="ChatGPT" className="w-[17px] h-[17px]" />
|
||||
Invisible</div>
|
||||
<div className="flex items-center justify-between gap-2 ml-[5px] px-2 py-[5px] text-xs text-gray-700 border-2 border-gray-200 rounded-full opacity-30">
|
||||
<img src={Gemini} alt="Gemini" className="w-[17px] h-[17px]" />Invisible</div>
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-[5px] text-xs text-gray-700 border-2 border-gray-200 rounded-full opacity-30">
|
||||
<img src={Preplextiy} alt="Perplexity" className="w-[17px] h-[17px]" />
|
||||
Invisible</div>
|
||||
</div>
|
||||
|
||||
<button className="flex items-center gap-1 px-1.5 py-1 rounded-md border border-gray-300 bg-white shadow-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16" fill="none">
|
||||
<path d="M6.59226 2H5.69226C3.75926 2 2.19226 3.567 2.19226 5.5V10.5C2.19226 12.433 3.75926 14 5.69226 14H10.6923C12.6253 14 14.1923 12.4333 14.1923 10.5003C14.1923 10.2415 14.1923 9.95005 14.1923 9.6" stroke="#65677D" strokeWidth="1.5" strokeLinecap="round" />
|
||||
<path d="M10.1923 2H14.1923M14.1923 2V6M14.1923 2L8.19128 8" stroke="#65677D" strokeWidth="1.5" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className="text-xs font-semibold text-gray-900">Share</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StrategyHero />
|
||||
<div className="grid grid-cols-1 gap-5 mb-4 lg:grid-cols-2">
|
||||
|
||||
<div className="flex items-start gap-5">
|
||||
<RankingsCard />
|
||||
<BacklinksCard />
|
||||
|
||||
|
||||
{showDetails ?
|
||||
<div className="flex flex-col justify-between gap-[38px] overflow-hidden bg-white border border-gray-300 shadow-sm rounded-xl">
|
||||
<div className="flex items-center gap-2 p-[23px] border-b border-gray-300 h-[60px]">
|
||||
<h2 className="text-base font-semibold text-gray-950">Ai Frequently Cited Sources in Healthcare</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between h-full gap-8 px-6 pb-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<DonutChart segments={healthcareSegments} size={200} strokeWidth={53} />
|
||||
|
||||
<div className="flex flex-col gap-1.5 flex-1 mt-2 ml-1.5">
|
||||
{healthcareItems.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center gap-2.5 px-2 py-[5px] rounded-md border hover:border-[#C9D7FB] hover:bg-[#ECF1FD] cursor-pointer ${item.highlighted ? "border-[#C9D7FB] bg-[#ECF1FD]" : "border-gray-300"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedItem(item);
|
||||
setShowDetails(false);
|
||||
}}
|
||||
>
|
||||
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: item.color }} />
|
||||
<div className="flex items-center gap-0.5 flex-1">
|
||||
<span className="text-sm">
|
||||
<span className="font-bold text-gray-900">{item.label.split(" ")[0]}</span>
|
||||
<span className="font-medium text-gray-900"> {item.label.split(" ").slice(1).join(" ")}</span>
|
||||
</span>
|
||||
{item.subtitle && (
|
||||
<span className="ml-1 text-sm text-gray-700">{item.subtitle}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col p-4 rounded-xl bg-[#ECF1FD]">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="18" viewBox="0 0 14 18" fill="none">
|
||||
<path d="M7.05074 3.62323C5.68916 3.62323 4.49145 4.39338 3.86719 5.52409" stroke="#17171B" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M4.27642 14.6968C4.55366 14.6212 4.83089 14.5456 5.10813 14.47C9.24146 13.2854 9.03984 13.3862 9.11545 13.2098C9.72033 11.1936 10.1236 11.748 11.2073 10.2106C11.7114 9.47974 12.0894 8.64804 12.2407 7.76592C12.3163 7.41308 12.3415 7.03503 12.3415 6.65698C12.3415 3.50657 9.79594 0.961044 6.67073 0.961044C3.54553 0.961044 1 3.50657 1 6.63178C1 7.51389 1.17642 8.42121 1.55447 9.22771C1.83171 9.83259 2.23496 10.3871 2.68862 10.8659C3.08885 11.2852 3.58998 11.6613 3.90862 12.1576" stroke="#17171B" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M5.28516 17.039L8.81361 16.0056" stroke="#17171B" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-950">How Ai and LLMs Choose Their Sources</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-900 leading-[19px]">
|
||||
Our analysis reveals distinct patterns in how leading AIs source and prioritize industry information. For example, ChatGPT leans heavily on user-generated content, while Google's AI Overviews favors authoritative tech review sites. Across the board, backlink quality plays a crucial role, with...{" "}
|
||||
<span className="text-[#4C60E5] underline cursor-pointer">read more</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
(
|
||||
<div className="flex flex-col w-full bg-white border border-gray-300 rounded-xl shadow-elevation">
|
||||
<div className="flex items-center gap-4 px-4 py-2 ">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="flex items-center justify-center gap-1.5 w-7 h-6 rounded-md border border-gray-300 bg-transparent hover:bg-gray-200 transition-colors"
|
||||
onClick={() => setShowDetails(true)}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 text-gray-800" strokeWidth={1.275} />
|
||||
</button>
|
||||
<h1 className="text-sm font-semibold">
|
||||
<span className="font-normal text-gray-800">Consumer Health Giants... </span>
|
||||
<span className="font-bold text-[#2B2D3B]">Good Lick With That </span>
|
||||
<span className="font-normal text-[#2B2D3B]">Tier</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full mx-auto border-t border-[#d0d7d8] shadow-xl ">
|
||||
<div className="h-1.5 bg-gradient-to-b from-[#0A2527]/8 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1 px-2 pb-2">
|
||||
{/* Table Header */}
|
||||
<div className="items-center hidden gap-3 px-6 py-2 font-semibold text-gray-800 border-b border-gray-300 md:flex text-body-s">
|
||||
<div className="flex items-center font-semibold gap-2 w-[160px] flex-shrink-0 text-[#65677D]">
|
||||
<span>Source</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[60px] flex-shrink-0 justify-center text-[#65677D]">
|
||||
<span>AIQ</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[233px] flex-shrink-0 text-[#65677D]">
|
||||
<span>How to Get Cited</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
<div className="flex items-center font-semibold gap-2 w-[180px] flex-shrink-0 text-[#65677D]">
|
||||
<span>Difficulty</span>
|
||||
<SortIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<div className="h-full px-2 overflow-y-auto">
|
||||
{tableData.map((row) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="flex flex-col md:flex-row items-start md:items-start border-b border-gray-200 bg-white px-6 pt-3 pb-6 hover:bg-gray-50 transition h-[30%] gap-3"
|
||||
>
|
||||
{/* Source */}
|
||||
<div className="flex items-center gap-2 w-[160px] flex-shrink-0">
|
||||
<div className="w-5 h-5 rounded-sm overflow-hidde">
|
||||
<img
|
||||
src={row.source.icon}
|
||||
alt={row.source.name}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
href={row.source.link}
|
||||
className="text-sm text-[#4C60E5] underline hover:no-underline truncatse"
|
||||
>
|
||||
{row.source.name}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* AIQ */}
|
||||
<div className="w-[60px] text-sm text-[#65677D] flex-shrink-0">
|
||||
{row.aiq}
|
||||
</div>
|
||||
|
||||
{/* How to Get Cited */}
|
||||
<div className="w-[233px] text-sm text-[#65677D] leading-snug flex-shrink-0">
|
||||
{row.howToGetCited}
|
||||
</div>
|
||||
|
||||
{/* Difficulty */}
|
||||
<div className="w-[180px] text-sm text-[#65677D] leading-snug flex-shrink-0">
|
||||
{row.difficulty}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-center items-center py-3.5 px-2 rounded-lg bg-[#F2F3F7]">
|
||||
<p className="text italic text-[#65677D] text-center max-w-[800px]">
|
||||
"Mass SEO factories disguised as medical authorities."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex flex-col gap-0 overflow-hidden bg-white border border-gray-300 shadow-sm rounded-xl">
|
||||
<div className="flex flex-col gap-7 px-4 py-3 border-b border-gray-300 h-[60px]">
|
||||
<div>
|
||||
<h2 className="text-base font-bold text-gray-950">Become the Source</h2>
|
||||
<p className="text-xs text-gray-700">
|
||||
Custom Authority Beacons & Backlink Strategy, Tailored for <span className="font-bold text-gray-950">Medical Spas</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 px-6 pr-[14px] pb-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<DonutChart segments={sourceSegments} size={200} strokeWidth={51.76} />
|
||||
|
||||
<div className="flex flex-col flex-1 gap-1.5 mt-[36px] ml-[19px]">
|
||||
{sourceItems.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-1">
|
||||
<div className="w-[10px] h-[10px] rounded-full flex-shrink-0 ml-[-5px] mt-[10px]" style={{ backgroundColor: item.color }} />
|
||||
<div className="flex flex-col ml-[5px] flex-1 gap-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="text-sm font-bold text-gray-900">{item.label.split(" ")[0]}</span>
|
||||
<MetricTooltip tooltipText={item.label.split(" ").slice(1).join(" ")} />
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">{item.label.split(" ").slice(1).join(" ")}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{item.images.map((imgSrc, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center justify-center overflow-hidden bg-white rounded-md w-7 h-7"
|
||||
>
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt=""
|
||||
className="object-cover w-full h-full rounded-md"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center mt-2 ml-1.5">
|
||||
<button className="w-[58%] px-2 py-6 rounded-full bg-white border-4 border-transparent bg-gradient-to-r from-blue-500 via-red-500 via-orange-400 via-yellow-400 to-green-500 bg-clip-padding relative group mx-auto hover:bg-[#ECF1FD] transition-colors">
|
||||
<div className="absolute inset-0 bg-white rounded-full m-[3px] flex items-center justify-center gap-3 hover:bg-[#ECF1FD] transition-colors">
|
||||
<div className="flex items-center justify-center w-6 h-6 bg-blue-600 rounded-full">
|
||||
<Zap className="w-4 h-4 text-white fill-white" />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
Add your Keywords and Become the Source
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StrategyHero />
|
||||
|
||||
<div className="flex items-start gap-5">
|
||||
<RankingsCard />
|
||||
<BacklinksCard />
|
||||
</div>
|
||||
</div>
|
||||
</ContainerPage>
|
||||
</>
|
||||
@@ -454,7 +482,7 @@ interface DonutChartProps {
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<div className="w-full border-b border-gray-300 bg-[#F6F8FE] px-2 py-2.5 mt-[-8px]">
|
||||
<div className="w-full border-b border-gray-300 bg-[#F6F8FE] px-2 py-2.5 mt-[21px]">
|
||||
<div className="flex items-center justify-center gap-1.5">
|
||||
<div className="flex h-[30px] w-[312px] items-center gap-1.5 rounded-lg border border-gray-300 bg-white p-0.5">
|
||||
<div className="flex items-center gap-1.5 flex-1">
|
||||
@@ -468,7 +496,7 @@ function Header() {
|
||||
</div>
|
||||
<div className="w-px h-5 bg-gray-300" />
|
||||
<div className="flex px-1.5 justify-between items-center flex-1 cursor-pointer">
|
||||
<span className="text-sm text-gray-900 tracking-tight">mysite.com</span>
|
||||
<span className="text-sm tracking-tight text-gray-900">mysite.com</span>
|
||||
<ChevronDown className="w-3 h-3 text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
import React from 'react'
|
||||
import { useState } from 'react'
|
||||
import Herosection from "../assets/herosection.svg"
|
||||
import Bgoverherosection from "../assets/bgoverherosection.png"
|
||||
import DashboardPreview from "../assets/MacBookPro.svg"
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { useAppDispatch, useAppSelector } from '../store/hooks'
|
||||
import { setUrl } from '../store/urlSlice'
|
||||
import { GetUser } from '../components/shared/GetUser'
|
||||
|
||||
const HomePage = () => {
|
||||
const [urlInput, setUrlInput] = useState('')
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
|
||||
// const user = useAppSelector((state) => state.url.isLoggedIn); // or change to your auth state
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (urlInput.trim()) {
|
||||
dispatch(setUrl(urlInput.trim()))
|
||||
|
||||
if (GetUser()) {
|
||||
navigate('/keywords');
|
||||
} else
|
||||
navigate('/login')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white overflow-x-hidden">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-x-0 top-0 h-[944px] bg-gradient-to-b from-[#112674] via-[#112674] to-transparent opacity-100"
|
||||
style={{ background: `url(${Herosection})`, backgroundSize: 'cover', backgroundPosition: 'center' }} />
|
||||
style={{ background: `url(${Bgoverherosection})`, backgroundSize: 'cover', backgroundPosition: 'center' }} />
|
||||
|
||||
<div className="absolute inset-x-0 top-0 h-[944px] opacity-full">
|
||||
<img
|
||||
src={Herosection}
|
||||
src={Bgoverherosection}
|
||||
alt=""
|
||||
className="w-full h-full object-cover mix-blend-overlay"
|
||||
/>
|
||||
@@ -129,9 +152,17 @@ const HomePage = () => {
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Your Website URL"
|
||||
value={urlInput}
|
||||
onChange={(e) => setUrlInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleButtonClick()
|
||||
}
|
||||
}}
|
||||
className="flex-1 px-4 py-2 sm:py-0 text-sm md:text-base text-[#65677D] bg-transparent outline-none placeholder:text-[#65677D]"
|
||||
/>
|
||||
<button
|
||||
onClick={handleButtonClick}
|
||||
className="px-3 md:px-4 py-2.5 rounded-[67px] text-white text-xs md:text-sm font-medium whitespace-nowrap"
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, #4A81FC 0%, #334EFD 100%)',
|
||||
|
||||
@@ -1,19 +1,266 @@
|
||||
import React from 'react'
|
||||
import { keywordSections } from "../assets/keyword-data";
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
// import { keywordSections as keywordSections3 } from "../assets/keyword-data";
|
||||
import { KeywordSection } from "../components/KeywordSection";
|
||||
import ContainerPage from '../components/shared/ContainerPage';
|
||||
import HeaderPage from '../components/shared/HeaderPage';
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
import type { IKeywordSection, KeywordRow } from '../assets/keyword-data';
|
||||
|
||||
// Function to process keywords in batches
|
||||
async function processInBatches<T, R>(
|
||||
items: T[],
|
||||
batchSize: number,
|
||||
processor: (item: T) => Promise<R>
|
||||
): Promise<R[]> {
|
||||
const results: R[] = []
|
||||
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize)
|
||||
console.log(`Processing batch ${Math.floor(i / batchSize) + 1}: ${batch.length} items`)
|
||||
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(item => processor(item))
|
||||
)
|
||||
|
||||
results.push(...batchResults)
|
||||
console.log(`Batch ${Math.floor(i / batchSize) + 1} completed`)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
interface AggregateResult {
|
||||
domain: string;
|
||||
keyword: string;
|
||||
local_rank: number | null;
|
||||
mentions: {
|
||||
chatgpt: boolean;
|
||||
gemini: boolean;
|
||||
google: boolean;
|
||||
perplexity: boolean;
|
||||
};
|
||||
serp_rank: number | null;
|
||||
}
|
||||
|
||||
const Keywords = () => {
|
||||
const url = useAppSelector((state) => state.url.url)
|
||||
const [keywordSections, setKeywordSections] = useState<IKeywordSection[]>([])
|
||||
const [flag, setFlag] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [loadingKeywords, setLoadingKeywords] = useState<Set<string>>(new Set())
|
||||
const hasFetchedAggregate = useRef(false)
|
||||
const initialKeywordCount = useRef<number>(0)
|
||||
const keywordsToFetch = useRef<string[]>([])
|
||||
|
||||
|
||||
// Fetch categorized keywords from API
|
||||
useEffect(() => {
|
||||
if (!url && flag) return
|
||||
|
||||
const fetchCategorizedKeywords = async () => {
|
||||
if (!url) return
|
||||
const apiUrl = `https://demo-funnel-1-63297b6a.hosted.cumin.dev/get_categorized_keywords?domain=${encodeURIComponent(url)}`
|
||||
try {
|
||||
const response = await fetch(apiUrl)
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
||||
const apiData = await response.json()
|
||||
|
||||
console.log("API Response Data:", apiData?.results)
|
||||
|
||||
// Transform API response to match IKeywordSection[] format
|
||||
// API returns: {bofu: ["keyword1", "keyword2"], mofu: [...], tofu: [...]}
|
||||
// We need: [{id: "bofu", title: "...", keywords: [KeywordRow[], ...}]
|
||||
|
||||
const sectionsConfig: Record<string, { title: string; subtitle: string; expanded: boolean }> = {
|
||||
bofu: {
|
||||
title: "Transactional, Immediate Business Impact, Highest Competition Opportunities (Bottom of Funnel - BoFu)",
|
||||
subtitle: "High Conversion Intent. Immediate Revenue Drivers.",
|
||||
expanded: true
|
||||
},
|
||||
mofu: {
|
||||
title: "Consideration Opportunities (Middle of Funnel - MoFu)",
|
||||
subtitle: "Research and Comparison Intent. Nurturing Leads.",
|
||||
expanded: false
|
||||
},
|
||||
tofu: {
|
||||
title: "Educational Opportunities (Top of Funnel - ToFu)",
|
||||
subtitle: "Informational Intent. Essential for building E-E-A-T and AI Trust.",
|
||||
expanded: false
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the results object into IKeywordSection[] array
|
||||
const transformedSections: IKeywordSection[] = Object.entries(apiData?.results || {}).map(([sectionId, keywordStrings]) => {
|
||||
const sectionConfig = sectionsConfig[sectionId] || {
|
||||
title: sectionId.toUpperCase(),
|
||||
subtitle: "",
|
||||
expanded: false
|
||||
}
|
||||
|
||||
// Convert string array to KeywordRow[] array
|
||||
const keywordRows: KeywordRow[] = (Array.isArray(keywordStrings) ? keywordStrings : []).map((keywordStr: string, index: number) => ({
|
||||
id: `${sectionId}-${index + 1}`,
|
||||
keyword: keywordStr,
|
||||
searchVolume: 0, // Will be updated later from aggregate API
|
||||
googlePos: 0,
|
||||
mapsPos: null,
|
||||
aiOverview: "invisible" as const,
|
||||
chatGPT: "invisible" as const,
|
||||
gemini: "invisible" as const,
|
||||
perplexity: "invisible" as const,
|
||||
competition: "light" as const,
|
||||
promptExplorer: "coming-soon" as const,
|
||||
status: "Add-Keyword" as const
|
||||
}))
|
||||
|
||||
return {
|
||||
id: sectionId,
|
||||
title: sectionConfig.title,
|
||||
subtitle: sectionConfig.subtitle,
|
||||
keywords: keywordRows,
|
||||
expanded: sectionConfig.expanded
|
||||
}
|
||||
})
|
||||
|
||||
// console.log("Transformed Sections:", transformedSections)
|
||||
setKeywordSections(transformedSections)
|
||||
setFlag(true)
|
||||
// Reset aggregate fetch flag and track initial count when new categories are loaded
|
||||
hasFetchedAggregate.current = false
|
||||
const totalKeywords = transformedSections.reduce((sum, section) => sum + section.keywords.length, 0)
|
||||
initialKeywordCount.current = totalKeywords
|
||||
} catch (error) {
|
||||
console.error('Error fetching categorized keywords:', error)
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchCategorizedKeywords()
|
||||
}, [url])
|
||||
|
||||
useEffect(() => {
|
||||
if (keywordSections.length > 0) {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [keywordSections])
|
||||
|
||||
|
||||
// Fetch aggregate results for each keyword after keywordSections are loaded
|
||||
// Only run once when keywordSections are first loaded (not when they update)
|
||||
useEffect(() => {
|
||||
if (!url || keywordSections.length === 0 || hasFetchedAggregate.current) return
|
||||
|
||||
// Only fetch if this is the initial load (keyword count matches initial count)
|
||||
const currentKeywordCount = keywordSections.reduce((sum, section) => sum + section.keywords.length, 0)
|
||||
if (currentKeywordCount !== initialKeywordCount.current || initialKeywordCount.current === 0) return
|
||||
|
||||
// Mark that we've started fetching to prevent re-fetching
|
||||
hasFetchedAggregate.current = true
|
||||
|
||||
// Store keywords to fetch at the start
|
||||
const allKeywords = keywordSections.flatMap(section =>
|
||||
section.keywords.map(row => row.keyword)
|
||||
)
|
||||
keywordsToFetch.current = allKeywords
|
||||
|
||||
// Mark all keywords as loading only once at the start
|
||||
setLoadingKeywords(new Set(allKeywords))
|
||||
|
||||
const fetchAggregateResults = async () => {
|
||||
try {
|
||||
|
||||
const fetchSingleKeyword = async (keyword: string) => {
|
||||
const encodedKeyword = encodeURIComponent(keyword)
|
||||
const encodedDomain = encodeURIComponent(url)
|
||||
const apiUrl = `https://code-server-cb9476d5e6f7.hosted.ghaymah.systems/proxy/8000/aggregate-results?keyword=${encodedKeyword}&domain=${encodedDomain}`
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl)
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
||||
const data: AggregateResult = await response.json()
|
||||
// console.log(`Fetched data for keyword "${keyword}":`, data)
|
||||
|
||||
// Update keywordSections with the new data immediately
|
||||
setKeywordSections(prevSections => {
|
||||
return prevSections.map(section => ({
|
||||
...section,
|
||||
keywords: section.keywords.map(kw => {
|
||||
if (kw.keyword === keyword) {
|
||||
// Map aggregate result to KeywordRow format
|
||||
return {
|
||||
...kw,
|
||||
googlePos: data.serp_rank ?? data.local_rank ?? 0,
|
||||
mapsPos: data.local_rank ?? null,
|
||||
aiOverview: data.mentions.google ? "visible" as const : "invisible" as const,
|
||||
chatGPT: data.mentions.chatgpt ? "visible" as const : "invisible" as const,
|
||||
gemini: data.mentions.gemini ? "visible" as const : "invisible" as const,
|
||||
perplexity: data.mentions.perplexity ? "visible" as const : "invisible" as const,
|
||||
}
|
||||
}
|
||||
return kw
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
// Remove from loading set
|
||||
setLoadingKeywords(prev => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(keyword)
|
||||
return newSet
|
||||
})
|
||||
|
||||
return { keyword, data }
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for keyword "${keyword}":`, error)
|
||||
// Remove from loading set even on error
|
||||
setLoadingKeywords(prev => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(keyword)
|
||||
return newSet
|
||||
})
|
||||
return { keyword, data: null, error }
|
||||
}
|
||||
}
|
||||
|
||||
const batchSize = 5
|
||||
await processInBatches(keywordsToFetch.current, batchSize, fetchSingleKeyword)
|
||||
|
||||
console.log('All keyword data fetched and updated')
|
||||
} catch (error) {
|
||||
console.error('Error fetching aggregate results:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchAggregateResults()
|
||||
}, [url, keywordSections])
|
||||
|
||||
return (
|
||||
<ContainerPage>
|
||||
<HeaderPage title="Keyword Visibility Matrix" buttonShow={true} />
|
||||
<div className='max-w-[1360px] mx-auto'>
|
||||
<HeaderPage title="Keyword Visibility Matrix" buttonShow={true} />
|
||||
|
||||
<div className="flex flex-col">
|
||||
{keywordSections.map((section) => (
|
||||
<KeywordSection key={section.id} section={section} />
|
||||
))}
|
||||
<div className="flex flex-col ">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold">Loading...</h2>
|
||||
<p className="text-gray-500">Please wait while we fetch the keywords</p>
|
||||
</div>
|
||||
</div>
|
||||
) : keywordSections?.length > 0 ? (
|
||||
keywordSections?.map((section) => (
|
||||
<KeywordSection key={section.id} section={section} loadingKeywords={loadingKeywords} />
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold">No keywords found</h2>
|
||||
<p className="text-gray-500">Please add a keyword to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ContainerPage>
|
||||
)
|
||||
|
||||
@@ -43,8 +43,8 @@ const Login = () => {
|
||||
if (result.success) {
|
||||
// Redirect or handle successful authentication
|
||||
console.log('Authentication successful:', result.data);
|
||||
// You can redirect here or handle the successful login
|
||||
window.location.href = '/'; // Example redirect
|
||||
// Redirect to keywords page after successful login
|
||||
window.location.href = '/keywords';
|
||||
} else {
|
||||
// Error is already set in the hook, no need to set it again
|
||||
console.error('Authentication failed:', result.error);
|
||||
|
||||
6
src/store/hooks.ts
Normal file
6
src/store/hooks.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
||||
import type { RootState, AppDispatch } from './store';
|
||||
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
||||
12
src/store/store.ts
Normal file
12
src/store/store.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import urlReducer from './urlSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
url: urlReducer,
|
||||
},
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
|
||||
49
src/store/urlSlice.ts
Normal file
49
src/store/urlSlice.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
interface UrlState {
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
// Load initial state from localStorage
|
||||
const loadUrlFromStorage = (): string | null => {
|
||||
try {
|
||||
const storedUrl = localStorage.getItem('app_url');
|
||||
return storedUrl ? storedUrl : null;
|
||||
} catch (error) {
|
||||
console.error('Error loading URL from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const initialState: UrlState = {
|
||||
url: loadUrlFromStorage(),
|
||||
};
|
||||
|
||||
const urlSlice = createSlice({
|
||||
name: 'url',
|
||||
initialState,
|
||||
reducers: {
|
||||
setUrl: (state, action: PayloadAction<string>) => {
|
||||
state.url = action.payload;
|
||||
// Save to localStorage
|
||||
try {
|
||||
localStorage.setItem('app_url', action.payload);
|
||||
} catch (error) {
|
||||
console.error('Error saving URL to localStorage:', error);
|
||||
}
|
||||
},
|
||||
clearUrl: (state) => {
|
||||
state.url = null;
|
||||
// Remove from localStorage
|
||||
try {
|
||||
localStorage.removeItem('app_url');
|
||||
} catch (error) {
|
||||
console.error('Error removing URL from localStorage:', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setUrl, clearUrl } = urlSlice.actions;
|
||||
export default urlSlice.reducer;
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم