Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions components/cover-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export default function CoverImage({
<Image
width={2000}
height={1000}
alt={`Cover Image for ${title}`}
src={coverImage?.node.sourceUrl}
className={`w-full h-auto object-cover${imgClassName ? ` ${imgClassName}` : ""}${slug ? " transition-transform duration-300 hover:scale-[1.01]" : ""}`}
priority={priority}
loading={priority ? "eager" : "lazy"}
sizes={sizes}
alt={title ? `Cover Image for ${title}` : "Cover Image"}
src={coverImage?.node.sourceUrl || "/blog/images/blog-bunny.png"}
className={cn("rounded-md transition-border duration-300", imgClassName, {
" transition-scale duration-300": slug,
})}
Comment on lines +32 to +34
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoverImage now uses cn(...) but the helper isn’t imported in this file, which will cause a runtime/compile failure. Also, the Tailwind classes transition-border and transition-scale don’t exist in this repo’s Tailwind config, so the intended transitions won’t apply; use valid Tailwind transition utilities (or extend Tailwind) instead.

Suggested change
className={cn("rounded-md transition-border duration-300", imgClassName, {
" transition-scale duration-300": slug,
})}
className={[
"rounded-md transition-colors duration-300",
slug ? "transition-transform duration-300" : "",
imgClassName ?? "",
]
.filter(Boolean)
.join(" ")}

Copilot uses AI. Check for mistakes.
priority
/>
);

Expand Down
7 changes: 6 additions & 1 deletion components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default function Footer() {
className="w-4 h-4 fill-current"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M 20.476562 0.00390625 L 24.464844 0.00390625 L 15.753906 10.167969 L 26 23.996094 L 17.976562 23.996094 L 11.691406 15.609375 L 4.503906 23.996094 L 0.511719 23.996094 L 9.828125 13.125 L 0 0.00390625 L 8.226562 0.00390625 L 13.90625 7.671875 Z M 19.078125 21.558594 L 21.285156 21.558594 L 7.027344 2.3125 L 4.65625 2.3125 Z M 19.078125 21.558594"
Expand All @@ -100,6 +101,7 @@ export default function Footer() {
className="w-4 h-4 fill-current"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
Expand All @@ -115,6 +117,7 @@ export default function Footer() {
className="w-4 h-4 fill-current"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
Expand All @@ -130,6 +133,7 @@ export default function Footer() {
className="w-4 h-4 fill-current"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M23.498 6.186a2.997 2.997 0 0 0-2.11-2.123C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.388.518A2.997 2.997 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a2.997 2.997 0 0 0 2.11 2.123c1.883.518 9.388.518 9.388.518s7.505 0 9.388-.518a2.997 2.997 0 0 0 2.11-2.123C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
Expand All @@ -145,6 +149,7 @@ export default function Footer() {
className="w-4 h-4 fill-current"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
</svg>
Expand Down Expand Up @@ -205,7 +210,7 @@ export default function Footer() {
</div>
<div className="my-12 pt-8 border-t border-gray-200">
<div className="flex flex-col-reverse md:flex-row md:items-center md:justify-between max-lg:ml-10">
<p className="mt-8 text-sm text-gray-500 md:mt-0"> Copyright © {new Date().getFullYear()} Keploy Inc</p>
<p className="mt-8 text-sm text-gray-500 md:mt-0"> Copyright © {new Date().getFullYear()} Keploy Inc</p>
</div>
</div>
</div>
Expand Down
8 changes: 2 additions & 6 deletions components/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ export default function Meta({
href="/blog/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/blog/favicon/site.webmanifest" />
<link
rel="mask-icon"
href="/blog/favicon/safari-pinned-tab.svg"
color="#000000"
/>
<link rel="shortcut icon" href="/blog/favicon/favicon.ico" />
<link rel="mask-icon" href="/blog/favicon/Group.svg" color="#000000" />
<link rel="shortcut icon" href="/blog/favicon/Group.png" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={Title} />
Expand Down
166 changes: 133 additions & 33 deletions components/testimonials.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import React from "react";
import React, { useState } from "react";
import { useRouter } from "next/router";
import { Marquee } from "./Marquee";
import Tweets from "../services/Tweets";
const firstRow = Tweets.slice(0, Tweets.length / 2);
const secondRow = Tweets.slice(Tweets.length / 2);

const firstRow = Tweets.slice(0, Math.ceil(Tweets.length / 2));
const secondRow = Tweets.slice(Math.ceil(Tweets.length / 2));

import { User } from "lucide-react";

/** Generates a two-letter initial from a name string */
const getInitials = (name: string): string => {
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) {
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}
return name.slice(0, 2).toUpperCase();
};

/** Fallback avatar component – renders a user icon/initials in a branded circle */
const FallbackAvatar = ({ name }: { name: string }) => (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
<User className="h-full w-full opacity-60" />
</div>
);

Comment on lines +20 to 26
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInitials is declared but never used, and FallbackAvatar accepts a name prop but doesn’t render initials (only the icon). This is dead code / misleading intent—either remove the unused helper/prop or render initials as the fallback so the behavior matches the comment.

Suggested change
/** Fallback avatar component – renders a user icon/initials in a branded circle */
const FallbackAvatar = ({ name }: { name: string }) => (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
<User className="h-full w-full opacity-60" />
</div>
);
/** Fallback avatar component – renders initials when available, otherwise a user icon */
const FallbackAvatar = ({ name }: { name: string }) => {
const initials = name.trim() ? getInitials(name) : "";
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
{initials ? (
<span className="text-sm font-medium leading-none">{initials}</span>
) : (
<User className="h-full w-full opacity-60" />
)}
</div>
);
};

Copilot uses AI. Check for mistakes.
const ReviewCard = ({
avatar,
Expand All @@ -17,59 +37,139 @@ const ReviewCard = ({
id: string;
content: string;
}) => {
const fallbackAvatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random&size=64`;
const localPlaceholder = "/blog/avatars/avatar-placeholder.svg";
const router = useRouter();
const [imgError, setImgError] = React.useState(false);

// Safe basePath handling for SSR/Hydration
const basePath = router?.basePath || "";

// Determine if we should use the proxy
const isExternal = typeof avatar === "string" && /^https?:\/\//i.test(avatar);
const proxiedAvatar = isExternal
? `${basePath}/api/proxy-image?url=${encodeURIComponent(avatar)}`
: avatar;

// Handle case where avatar might be empty or invalid
React.useEffect(() => {
if (!avatar) {
setImgError(true);
} else {
setImgError(false);
}
}, [avatar]);

return (
<a href={post} target="_blank" rel="noopener noreferrer" className="lg:mx-2">
<figure className="relative w-80 cursor-pointer overflow-hidden rounded-xl border p-4 border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05]">
<div className="flex flex-row items-center gap-2">
<img
className="rounded-full"
width="32"
height="32"
alt={`${name}'s avatar`}
src={avatar}
onError={(e) => {
const img = e.target as HTMLImageElement;
if (img.src !== localPlaceholder) {
img.onerror = () => { img.src = localPlaceholder; };
img.src = fallbackAvatar;
}
}}
/>
<a
href={post}
target="_blank"
rel="noopener noreferrer"
className="mx-2 block"
>
<figure className="group/card relative w-[320px] cursor-pointer overflow-hidden rounded-2xl border border-gray-200 bg-white p-5 shadow-md transition-all duration-300 hover:shadow-xl hover:-translate-y-1">
{/* Quote icon */}
<svg
className="absolute right-4 top-4 h-8 w-8 text-primary-200 opacity-60"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10H14.017zM0 21v-7.391c0-5.704 3.731-9.57 8.983-10.609L9.978 5.151c-2.432.917-3.995 3.638-3.995 5.849h4v10H0z" />
</svg>

<div className="flex flex-row items-center gap-3">
<div className="h-10 w-10 relative shrink-0">
{imgError ? (
<FallbackAvatar name={name} />
) : (
<img
className="h-full w-full rounded-full object-cover ring-2 ring-primary-100"
width={40}
height={40}
alt={name ? `${name}'s avatar` : "Avatar"}
src={proxiedAvatar}
onError={() => setImgError(true)}
loading="lazy"
/>
)}
</div>
<div className="flex flex-col">
<figcaption className="text-sm font-bold">{name}</figcaption>
<p className="text-xs font-medium ">{id}</p>
<figcaption className="text-sm font-semibold text-gray-900 leading-tight">
{name}
</figcaption>
<p className="text-xs font-medium text-gray-500">@{id}</p>
</div>
</div>
<blockquote className="mt-2 text-sm">{content}</blockquote>

<blockquote className="mt-3 line-clamp-4 text-sm leading-relaxed text-gray-600">
{content}
</blockquote>

{/* Bottom accent bar */}
<div className="absolute bottom-0 left-0 h-1 w-full bg-gradient-to-r from-primary-300 to-primary-100 opacity-0 transition-opacity duration-300 group-hover/card:opacity-100" />
</figure>
</a>
);
};


const TwitterTestimonials = () => {
const [mounted, setMounted] = useState(false);

React.useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return (
<section className="py-12 md:py-16">
<h3 className="text-left bg-gradient-to-r from-orange-200 to-orange-100 bg-[length:100%_20px] bg-no-repeat bg-left-bottom w-max mb-8 text-3xl lg:text-4xl heading1 md:text-4xl font-bold tracking-tighter leading-tight">
What our community thinks
</h3>
<div className="h-40 flex items-center justify-center">
<div className="animate-pulse flex space-x-4">
<div className="rounded-full bg-slate-200 h-10 w-10"></div>
<div className="flex-1 space-y-6 py-1">
<div className="h-2 bg-slate-200 rounded"></div>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-4">
<div className="h-2 bg-slate-200 rounded col-span-2"></div>
<div className="h-2 bg-slate-200 rounded col-span-1"></div>
</div>
<div className="h-2 bg-slate-200 rounded"></div>
</div>
</div>
</div>
</div>
</section>
);
}

return (
<div className="">
<h3 className="text-center lg:text-left bg-gradient-to-r from-orange-200 to-orange-100 bg-[length:100%_20px] bg-no-repeat bg-left-bottom w-max mb-6 text-3xl lg:text-4xl heading1 md:text-4xl font-bold tracking-tighter leading-tight mt-16">
<section className="py-12 md:py-16">
<h3 className="text-left bg-gradient-to-r from-orange-200 to-orange-100 bg-[length:100%_20px] bg-no-repeat bg-left-bottom w-max mb-8 text-3xl lg:text-4xl heading1 md:text-4xl font-bold tracking-tighter leading-tight">
What our community thinks
</h3>
<div className="relative flex mb-8 h-[700px] w-full flex-col items-center justify-center overflow-hidden rounded-lg bg-transparent marquee-mask">

<Marquee pauseOnHover repeat={2} className="[--duration:17s]">
<div className="relative flex flex-col items-center justify-center gap-4 overflow-hidden rounded-xl py-4">
{/* Row 1 – scrolls left */}
<Marquee pauseOnHover className="[--duration:11s]">
{firstRow.map((tweet) => (
<ReviewCard key={tweet.id} {...tweet} />
<ReviewCard key={tweet.id + "-r1"} {...tweet} />
))}
</Marquee>
<Marquee reverse pauseOnHover repeat={2} className="[--duration:17s]">

{/* Row 2 – scrolls right */}
<Marquee reverse pauseOnHover className="[--duration:11s]">
{secondRow.map((tweet) => (
<ReviewCard key={tweet.id} {...tweet} />
<ReviewCard key={tweet.id + "-r2"} {...tweet} />
))}
</Marquee>

{/* Gradient fade overlays */}
<div className="pointer-events-none absolute inset-y-0 left-0 w-1/4 bg-gradient-to-r from-white to-transparent" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-1/4 bg-gradient-to-l from-white to-transparent" />
</div>
</div>
</section>
);
};

Expand Down
16 changes: 16 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ module.exports = {
key: 'Content-Security-Policy',
value: contentSecurityPolicy,
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
]
Expand Down
11 changes: 4 additions & 7 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ export default function Index({ communityPosts, technologyPosts, preview }) {
canonicalUrl={SITE_URL}
ogType="website"
>
<Head>
<title>{`Engineering | Keploy Blog`}</title>
</Head>
<Header />
<Container>
<div className="">
Expand Down Expand Up @@ -85,7 +82,7 @@ export default function Index({ communityPosts, technologyPosts, preview }) {

export const getStaticProps: GetStaticProps = async ({ preview = false }) => {
const allCommunityPosts = await getAllPostsForCommunity(preview);
const allTehcnologyPosts = await getAllPostsForTechnology(preview);
const allTechnologyPosts = await getAllPostsForTechnology(preview);

return {
props: {
Expand All @@ -94,9 +91,9 @@ export const getStaticProps: GetStaticProps = async ({ preview = false }) => {
? allCommunityPosts?.edges?.slice(0, 3)
: allCommunityPosts?.edges,
technologyPosts:
allTehcnologyPosts?.edges?.length > 3
? allTehcnologyPosts?.edges?.slice(0, 3)
: allTehcnologyPosts.edges,
allTechnologyPosts?.edges?.length > 3
? allTechnologyPosts?.edges?.slice(0, 3)
: allTechnologyPosts.edges,
preview,
},
revalidate: 10,
Expand Down