diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 518d6ce..cfacf74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-helmet-async": "^3.0.0", "react-use": "^17.6.0", "shadcn": "^4.0.6", "tailwind-merge": "^3.5.0", @@ -6291,6 +6292,15 @@ "css-in-js-utils": "^3.1.0" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -6991,6 +7001,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7955,6 +7977,26 @@ "react": "^19.2.4" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", + "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -8489,6 +8531,12 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3b99dd0..78ec1af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-helmet-async": "^3.0.0", "react-use": "^17.6.0", "shadcn": "^4.0.6", "tailwind-merge": "^3.5.0", diff --git a/frontend/src/components/SEO.tsx b/frontend/src/components/SEO.tsx new file mode 100644 index 0000000..f2ac0cf --- /dev/null +++ b/frontend/src/components/SEO.tsx @@ -0,0 +1,64 @@ +import { Helmet } from 'react-helmet-async'; +import { useLocation } from 'react-router-dom'; + +interface SEOProps { + title: string; + description: string; + canonical?: string; + image?: string; + type?: string; + robots?: string; + keywords?: string; + twitterCard?: string; +} + +export default function SEO({ + title, + description, + canonical, + image, + type = 'website', + robots = 'index,follow', + keywords, + twitterCard = 'summary_large_image', +}: SEOProps) { + const location = useLocation(); + const siteUrl = 'https://codex-iter.in'; + const url = canonical || `${siteUrl}${location.pathname}`; + + // Fallback to a global default social preview image if none provided + const defaultImage = `${siteUrl}/codex_dark.png`; // Using the existing icon as default + const ogImage = image || defaultImage; + + return ( + + {/* Basic Metadata */} + {title} + + + {keywords && } + + {/* Canonical URL */} + + + {/* Open Graph / Facebook */} + + + + + + + + + {/* Twitter */} + + + + + + + {/* Viewport & Language (usually in index.html, but safe to reinforce if needed) */} + + + ); +} diff --git a/frontend/src/components/layout/Footer.tsx b/frontend/src/components/layout/Footer.tsx index 5c72d8c..d209cea 100644 --- a/frontend/src/components/layout/Footer.tsx +++ b/frontend/src/components/layout/Footer.tsx @@ -78,13 +78,15 @@ export default function Footer() { Connect
{[ - { icon: , href: settings?.linkedin || "#", id: "linkedin" }, - { icon: , href: settings?.email ? `mailto:${settings.email}` : "#", id: "email" }, - { icon: , href: settings?.instagram || "#", id: "instagram" }, + { icon: , href: settings?.linkedin || "https://www.linkedin.com/company/codex-iter", id: "linkedin" }, + { icon: , href: settings?.email ? `mailto:${settings.email}` : "mailto:codexiter@gmail.com", id: "email" }, + { icon: , href: settings?.instagram || "https://www.instagram.com/codexiter", id: "instagram" }, ].map(({ icon, href, id }) => (
- {scrolled ? : } + {scrolled ? CODEX ITER Logo : CODEX ITER Logo}
CODEX ITER diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..0a361e5 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { HelmetProvider } from 'react-helmet-async' import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/frontend/src/pages/BlogDetail.tsx b/frontend/src/pages/BlogDetail.tsx index 853ec3e..c00a10a 100644 --- a/frontend/src/pages/BlogDetail.tsx +++ b/frontend/src/pages/BlogDetail.tsx @@ -4,6 +4,7 @@ import { client } from "../sanity/client"; import { urlFor } from "../sanity/image"; import { PortableText } from "@portabletext/react"; import { ScrollReveal } from "../components/animations/ScrollReveal"; +import SEO from "../components/SEO"; export default function BlogDetail() { const { slug } = useParams<{ slug: string }>(); @@ -58,6 +59,13 @@ export default function BlogDetail() { return (
+
diff --git a/frontend/src/pages/Blogs.tsx b/frontend/src/pages/Blogs.tsx index a9e788b..3577e68 100644 --- a/frontend/src/pages/Blogs.tsx +++ b/frontend/src/pages/Blogs.tsx @@ -5,6 +5,7 @@ import { ScrollReveal, StaggerContainer, StaggerItem } from "../components/anima import { useEffect, useState } from "react"; import { client } from "../sanity/client"; import { urlFor } from "../sanity/image"; +import SEO from "../components/SEO"; export default function Blogs() { const { hero } = mockData.blogs; @@ -68,6 +69,10 @@ export default function Blogs() { return (
+
{/* Header Section */} diff --git a/frontend/src/pages/Events.tsx b/frontend/src/pages/Events.tsx index 55e8800..6117ddf 100644 --- a/frontend/src/pages/Events.tsx +++ b/frontend/src/pages/Events.tsx @@ -6,6 +6,7 @@ import TiltCard from "../components/animations/TiltCard"; import { useEffect, useState } from "react"; import { client } from "../sanity/client"; import { urlFor } from "../sanity/image"; +import SEO from "../components/SEO"; const prefersReduced = typeof window !== "undefined" && @@ -194,6 +195,10 @@ export default function Events() { return (
+
{/* Header */} @@ -201,9 +206,9 @@ export default function Events() {
{hero.label}
-

+

{hero.titlePart1}
{hero.titlePart2} -

+

{hero.description}

diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 0c7feae..e2531d0 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -6,6 +6,7 @@ import { ScrollReveal, StaggerContainer, StaggerItem } from "../components/anima import main_logo from "../assets/main_logo.png" import { client } from "../sanity/client"; import { urlFor } from "../sanity/image"; +import SEO from "../components/SEO"; const prefersReduced = typeof window !== "undefined" && @@ -134,6 +135,10 @@ export default function Home() { return (
+ {/* ── HERO ────────────────────────────────────────────────── */}
@@ -147,7 +152,7 @@ export default function Home() { {hero.established} -

+

{" "}
@@ -155,7 +160,7 @@ export default function Home() { {" "}
-

+

{/* Typing subheading */}

@@ -199,7 +204,7 @@ export default function Home() {

- + CODEX ITER Main Logo
{hero.loadingText}
diff --git a/frontend/src/pages/Team.tsx b/frontend/src/pages/Team.tsx index 89ee295..c3c3666 100644 --- a/frontend/src/pages/Team.tsx +++ b/frontend/src/pages/Team.tsx @@ -5,6 +5,7 @@ import TiltCard from "../components/animations/TiltCard"; import { useEffect, useState, useRef } from "react"; import { client } from "../sanity/client"; import { urlFor } from "../sanity/image"; +import SEO from "../components/SEO"; const prefersReduced = typeof window !== "undefined" && @@ -152,6 +153,10 @@ export default function Team() { return (
+
{/* Hero */} @@ -159,9 +164,9 @@ export default function Team() {
{hero.label}
-

+

{hero.titlePart1}
{hero.titlePart2} -

+

{hero.description}

diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1efc49a..4d70c58 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,8 +9,6 @@ export default defineConfig({ sitemap({ hostname: 'https://codex-iter.in', dynamicRoutes: [ - '/', - '/about', '/blogs', '/events', '/team'