This commit is contained in:
Iliyan Angelov
2025-11-24 03:52:08 +02:00
parent dfcaebaf8c
commit 366f28677a
18241 changed files with 865352 additions and 567 deletions

View File

@@ -0,0 +1,185 @@
"use client";
import { useState, useEffect } from "react";
import Image from "next/legacy/image";
import Link from "next/link";
import { useCaseStudies, useClients } from "@/lib/hooks/useCaseStudy";
import { getImageUrl } from "@/lib/imageUtils";
import one from "@/public/images/case/one.png";
const CaseItems = () => {
const [activeTabIndex, setActiveTabIndex] = useState(0);
const { caseStudies, loading: casesLoading } = useCaseStudies();
const { clients, loading: clientsLoading } = useClients();
const handleTabClick = (index: number) => {
setActiveTabIndex(index);
};
// Filter case studies by category
const caseStudiesData = caseStudies.filter((cs) => !cs.client);
const clientCaseStudies = caseStudies.filter((cs) => cs.client);
if (casesLoading || clientsLoading) {
return (
<section className="fix-top pb-120 c-study">
<div className="container">
<div className="row">
<div className="col-12">
<p className="text-center">Loading case studies...</p>
</div>
</div>
</div>
</section>
);
}
return (
<section className="fix-top pb-120 c-study">
<div className="container">
<div className="row">
<div className="col-12">
<div className="c-study-banner pb-120">
<div className="row">
<div className="col-12 col-lg-9">
<h2 className="mt-8 title-anim fw-7 text-secondary mb-24">
Case Studies
</h2>
<p className="cur-lg">
Discover how we help enterprises solve complex challenges with
secure, scalable solutions. Our case studies highlight real
business outcomes accelerated delivery, reduced costs,
improved reliability, and data-driven growth powered by modern
cloud, AI, and platform engineering.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="c-study-inner pt-120 mb-60">
<div className="c-study-btns">
<button
className={`study-btn ${
activeTabIndex === 0 ? "study-btn-active" : ""
}`}
onClick={() => handleTabClick(0)}
>
Case Study
</button>
<span></span>
<button
className={`study-btn ${
activeTabIndex === 1 ? "study-btn-active" : ""
}`}
onClick={() => handleTabClick(1)}
>
Client
</button>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="c-content-wrapper mt-60">
<div
className={`c-tab-single ${
activeTabIndex === 0 ? "active-tab" : ""
}`}
>
<div className="row vertical-column-gap-lg">
{caseStudiesData.map((caseStudy) => (
<div key={caseStudy.id} className="col-12 col-lg-6">
<div className="c-study-single">
<div className="thumb mb-24">
<Link href={`/case-study/${caseStudy.slug}`} className="w-100">
<Image
src={caseStudy.thumbnail ? getImageUrl(caseStudy.thumbnail) : one}
className="w-100 mh-300"
alt={caseStudy.title}
width={600}
height={400}
/>
</Link>
</div>
<div className="content">
<Link href={`/case-study/${caseStudy.slug}`} className="mb-30 fw-6">
{caseStudy.category_name || 'Case Study'}
</Link>
<h4 className="fw-6 mt-8 text-secondary">
<Link href={`/case-study/${caseStudy.slug}`}>
{caseStudy.title}
</Link>
</h4>
</div>
</div>
</div>
))}
{caseStudiesData.length === 0 && (
<div className="col-12">
<p className="text-center">No case studies found.</p>
</div>
)}
</div>
</div>
<div
className={`c-tab-single ${
activeTabIndex === 1 ? "active-tab" : ""
}`}
>
<div className="row vertical-column-gap-lg">
{clientCaseStudies.map((caseStudy, index) => (
<div key={caseStudy.id} className="col-12">
<div className={`row vertical-column-gap-md align-items-center ${index % 2 === 1 ? 'flex-row-reverse' : ''}`}>
<div className="col-12 col-lg-6">
<div className="c-tab__client">
<h2 className="mt-8 fw-7 title-anim text-secondary mb-24">
{caseStudy.client?.name || caseStudy.title}
</h2>
<p className="cur-lg">
{caseStudy.excerpt || caseStudy.client?.description}
</p>
<div className="mt-40">
<Link
href={`/case-study/${caseStudy.slug}`}
className="btn-anim btn-anim-light"
>
Read More
<i className="fa-solid fa-arrow-trend-up"></i>
<span></span>
</Link>
</div>
</div>
</div>
<div className={`col-12 col-lg-6 col-xxl-5 ${index % 2 === 0 ? 'offset-xxl-1' : ''}`}>
<div className="c-tab__thumb">
<Image
src={caseStudy.thumbnail ? getImageUrl(caseStudy.thumbnail) : one}
className="w-100 mh-300"
alt={caseStudy.client?.name || caseStudy.title}
width={600}
height={400}
/>
</div>
</div>
</div>
</div>
))}
{clientCaseStudies.length === 0 && (
<div className="col-12">
<p className="text-center">No client case studies found.</p>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default CaseItems;

View File

@@ -0,0 +1,143 @@
"use client";
import { use } from 'react';
import Image from "next/legacy/image";
import { useCaseStudy } from "@/lib/hooks/useCaseStudy";
import { getImageUrl } from "@/lib/imageUtils";
import poster from "@/public/images/case/poster.png";
import project from "@/public/images/case/project.png";
import nine from "@/public/images/case/nine.png";
interface CaseSingleProps {
slug: string;
}
const CaseSingle = ({ slug }: CaseSingleProps) => {
const { caseStudy, loading, error } = useCaseStudy(slug);
if (loading) {
return (
<section className="c-details fix-top pb-120">
<div className="container">
<div className="row">
<div className="col-12">
<p className="text-center">Loading case study...</p>
</div>
</div>
</div>
</section>
);
}
if (error || !caseStudy) {
return (
<section className="c-details fix-top pb-120">
<div className="container">
<div className="row">
<div className="col-12">
<p className="text-center text-danger">Case study not found.</p>
</div>
</div>
</div>
</section>
);
}
return (
<section className="c-details fix-top pb-120">
<div className="container">
<div className="row">
<div className="col-12">
<div className="c-details-intro">
<h2 className="mt-8 text-secondary title-anim fw-7">
{caseStudy.title}
</h2>
{caseStudy.subtitle && (
<h4 className="mt-4 text-secondary">{caseStudy.subtitle}</h4>
)}
<div className="poster mt-60 fade-top">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={caseStudy.poster_image ? getImageUrl(caseStudy.poster_image) : poster}
className="w-100 parallax-image mh-300"
alt={caseStudy.title}
width={1200}
height={600}
/>
</div>
</div>
</div>
<div className="row vertical-column-gap align-items-center pt-120 pb-120">
<div className="col-12 col-lg-6">
<div className="c-details-content">
<h2 className="mt-8 fw-7 text-secondary title-anim mb-24">
Project
</h2>
{caseStudy.project_overview ? (
<p className="cur-lg">{caseStudy.project_overview}</p>
) : (
<div
className="cur-lg"
dangerouslySetInnerHTML={{ __html: caseStudy.description || '' }}
/>
)}
</div>
</div>
<div className="col-12 col-lg-6 col-xxl-5 offset-xxl-1 fade-wrapper">
<div className="c-details-thumb fade-top">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={caseStudy.project_image ? getImageUrl(caseStudy.project_image) : project}
className="w-100 parallax-image mh-260"
alt={`${caseStudy.title} - Project`}
width={600}
height={500}
/>
</div>
</div>
</div>
</div>
</div>
{caseStudy.site_map_content && (
<div className="row">
<div className="col-12">
<div className="road-map__content">
<h2 className="mt-8 fw-7 text-secondary title-anim mb-24">
Site Map
</h2>
<p className="cur-lg">{caseStudy.site_map_content}</p>
</div>
</div>
</div>
)}
{caseStudy.gallery_images && caseStudy.gallery_images.length > 0 && (
<div className="row vertical-column-gap mt-60 fade-wrapper">
{caseStudy.gallery_images.map((image) => (
<div key={image.id} className="col-12 col-sm-6 col-xl-3">
<div className="c-details-thumb fade-top">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={getImageUrl(image.image) || nine}
className="w-100 mh-300 parallax-image"
alt={image.caption || caseStudy.title}
width={300}
height={300}
/>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
</section>
);
};
export default CaseSingle;

View File

@@ -0,0 +1,65 @@
"use client";
import dynamic from "next/dynamic";
const SmoothScroll = dynamic(() => import("../../shared/layout/animations/SmoothScroll"), {
ssr: false,
});
const ParallaxImage = dynamic(() => import("../../shared/layout/animations/ParallaxImage"), {
ssr: false,
});
const FadeImageBottom = dynamic(() => import("../../shared/layout/animations/FadeImageBottom"), {
ssr: false,
});
const ButtonHoverAnimation = dynamic(
() => import("../../shared/layout/animations/ButtonHoverAnimation"),
{
ssr: false,
}
);
const VanillaTiltHover = dynamic(
() => import("../../shared/layout/animations/VanillaTiltHover"),
{
ssr: false,
}
);
const SplitTextAnimations = dynamic(
() => import("../../shared/layout/animations/SplitTextAnimations"),
{
ssr: false,
}
);
const ScrollToElement = dynamic(() => import("../../shared/layout/animations/ScrollToElement"), {
ssr: false,
});
const AppearDown = dynamic(() => import("../../shared/layout/animations/AppearDown"), {
ssr: false,
});
const FadeAnimations = dynamic(() => import("../../shared/layout/animations/FadeAnimations"), {
ssr: false,
});
const CaseStudyInitAnimations = () => {
return (
<>
<SmoothScroll />
<ParallaxImage />
<FadeImageBottom />
<ButtonHoverAnimation />
<VanillaTiltHover />
<SplitTextAnimations />
<ScrollToElement />
<AppearDown />
<FadeAnimations />
</>
);
};
export default CaseStudyInitAnimations;

View File

@@ -0,0 +1,67 @@
"use client";
import { useState, useEffect, useRef } from "react";
const CaseStudyScrollProgressButton = () => {
useEffect(() => {
window.scroll(0, 0);
}, []);
const [scrollProgress, setScrollProgress] = useState(0);
const [isActive, setIsActive] = useState(false);
const scrollRef = useRef<HTMLButtonElement>(null);
const handleScroll = () => {
const totalHeight = document.body.scrollHeight - window.innerHeight;
const progress = (window.scrollY / totalHeight) * 100;
setScrollProgress(progress);
setIsActive(window.scrollY > 50);
};
const handleProgressClick = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
useEffect(() => {
window.scrollTo(0, 0);
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return (
<button
ref={scrollRef}
className={`progress-wrap ${isActive ? " active-progress" : " "}`}
onClick={handleProgressClick}
title="Go To Top"
>
<span></span>
<svg
className="progress-circle svg-content"
width="100%"
height="100%"
viewBox="-1 -1 102 102"
>
<path
d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98"
stroke="#3887FE"
strokeWidth="4"
fill="none"
style={{
strokeDasharray: "308.66px",
strokeDashoffset: `${308.66 - scrollProgress * 3.0866}px`,
}}
/>
</svg>
</button>
);
};
export default CaseStudyScrollProgressButton;

View File

@@ -0,0 +1,52 @@
"use client";
import { useCaseStudy } from "@/lib/hooks/useCaseStudy";
interface ProcessProps {
slug: string;
}
const Process = ({ slug }: ProcessProps) => {
const { caseStudy, loading } = useCaseStudy(slug);
if (loading || !caseStudy || !caseStudy.process_steps || caseStudy.process_steps.length === 0) {
return null;
}
return (
<section className="pt-120 pb-120 tp-process bg-black sticky-wrapper">
<div className="container">
<div className="row vertical-column-gap">
<div className="col-12 col-lg-6">
<div className="process__content sticky-item">
<h2 className="mt-8 title-anim text-white fw-7 mb-24">
{caseStudy.title} Process
</h2>
<p className="cur-lg text-quinary">
{caseStudy.excerpt}
</p>
</div>
</div>
<div className="col-12 col-lg-6 col-xxl-5 offset-xxl-1">
<div className="process__thumb sticky-item">
{caseStudy.process_steps.map((step) => (
<div key={step.id} className="process__single">
<span className="op-text text-white mb-40 cur-lg">
{String(step.step_number).padStart(2, '0')}
</span>
<h5 className="mt-8 text-white mb-24 title-anim">
{step.title}
</h5>
<p className="cur-lg text-quinary">
{step.description}
</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
};
export default Process;

View File

@@ -0,0 +1,67 @@
"use client";
import Image from "next/legacy/image";
import Link from "next/link";
import { useCaseStudy } from "@/lib/hooks/useCaseStudy";
import { getImageUrl } from "@/lib/imageUtils";
import one from "@/public/images/case/one.png";
interface RelatedCaseProps {
slug: string;
}
const RelatedCase = ({ slug }: RelatedCaseProps) => {
const { caseStudy, loading } = useCaseStudy(slug);
if (loading || !caseStudy || !caseStudy.related_case_studies || caseStudy.related_case_studies.length === 0) {
return null;
}
return (
<section className="pt-120 pb-120 c-study fade-wrapper">
<div className="container">
<div className="row">
<div className="col-12 col-lg-9">
<h2 className="mt-8 title-anim fw-7 text-secondary mb-24">
Similar Case Studies
</h2>
</div>
</div>
<div className="row vertical-column-gap-lg">
{caseStudy.related_case_studies.slice(0, 2).map((relatedCase) => (
<div key={relatedCase.id} className="col-12 col-lg-6">
<div className="c-study-single fade-top">
<div className="thumb mb-24">
<Link href={`/case-study/${relatedCase.slug}`} className="w-100">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={relatedCase.thumbnail ? getImageUrl(relatedCase.thumbnail) : one}
className="w-100 mh-300 parallax-image"
alt={relatedCase.title}
width={600}
height={400}
/>
</div>
</div>
</Link>
</div>
<div className="content">
<Link href={`/case-study/${relatedCase.slug}`} className="mb-30 fw-6">
{relatedCase.category_name || 'Case Study'}
</Link>
<h4 className="fw-6 mt-8 text-secondary">
<Link href={`/case-study/${relatedCase.slug}`}>
{relatedCase.title}
</Link>
</h4>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default RelatedCase;