4 الالتزامات

المؤلف SHA1 الرسالة التاريخ
e9d6cd1562 Update img and desgin 2025-11-06 13:01:18 +02:00
db38660fc9 Add API To App & Redux Tool kit 2025-11-06 12:31:09 +02:00
e4ad2acf6c finish the checkform 2 2025-10-28 15:32:10 +02:00
1131dc8527 finish the checkform 2025-10-28 15:30:04 +02:00
24 ملفات معدلة مع 1572 إضافات و662 حذوفات

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

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 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>
);
}

عرض الملف

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

عرض الملف

@@ -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
عرض الملف

@@ -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
عرض الملف

@@ -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
عرض الملف

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