19 Commits

Author SHA1 Message Date
26e3d0975f Merge pull request #34 from OwethuManagedServices/09-09-2025-/Please-add-a-scroller-onTrusted-By-Industry-Leaders
Fix : Added a scroller on ClientLogoSection
2025-09-17 10:47:20 +02:00
4e6d0a5512 Fix : Added a scroller on ClientLogoSection 2025-09-17 10:44:00 +02:00
f0c68f549d Merge pull request #33 from OwethuManagedServices/09-09-2025-/Please-add-a-scroller-onTrusted-By-Industry-Leaders
feat: enhance ClientLogosSection with scrolling functionality and con…
2025-09-16 16:52:11 +02:00
2fade93bf7 feat: enhance ClientLogosSection with scrolling functionality and controls 2025-09-16 16:47:39 +02:00
33008c17f9 Merge pull request #32 from OwethuManagedServices/29-08-2025/Add-CVEvolve-Link-Under-OMS-Website-Products-Menu
29 08 2025/add cv evolve link under oms website products menu
2025-09-08 12:09:05 +02:00
8e889d5e53 fix: update product link and improve dropdown layout in HeaderClient 2025-09-08 11:49:23 +02:00
0530c9c943 fix:cvevolve link+seo+sm links 2025-09-04 15:39:37 +02:00
98f581b348 Merge pull request #31 from OwethuManagedServices/dev
Dev
2025-05-28 10:19:35 +02:00
45822f9af8 Merge pull request #29 from OwethuManagedServices/dev
Dev
2025-05-28 09:07:43 +02:00
af917f3484 Merge pull request #26 from OwethuManagedServices/dev
Dev
2025-05-27 16:45:09 +02:00
2044f7207e Merge pull request #24 from OwethuManagedServices/dev
Dev
2025-05-27 15:45:27 +02:00
4f71f687d6 Merge pull request #22 from OwethuManagedServices/dev
Dev
2025-05-27 15:33:02 +02:00
78e74dfc7d Merge pull request #20 from OwethuManagedServices/dev
Dev
2025-05-27 02:57:45 +02:00
9949158d31 Merge pull request #18 from OwethuManagedServices/dev
Dev
2025-05-26 22:32:33 +02:00
985f5c43ba Merge pull request #16 from OwethuManagedServices/dev
Dev
2025-05-26 16:42:30 +02:00
6f3c946845 Merge pull request #14 from OwethuManagedServices/dev
Dev
2025-05-26 11:29:34 +02:00
d1c497c936 Merge pull request #12 from OwethuManagedServices/dev
Dev
2025-05-26 10:02:29 +02:00
af744bd192 Merge pull request #10 from OwethuManagedServices/dev
Dev
2025-05-26 09:53:57 +02:00
730dc51629 Merge pull request #2 from OwethuManagedServices/05-05-2025/Add-Toyota-on-Trusted-by-Partners
05 05 2025/add toyota on trusted by partners
2025-05-08 13:48:18 +02:00
12 changed files with 198 additions and 53 deletions

View File

@ -1,42 +1,87 @@
// components/ClientLogosSection.tsx
import React from "react";
"use client";
import { useRef, useEffect, useState } from "react";
import Image from "next/image";
// Define structure for client data - focusing on logos now
type Client = {
name: string;
logoUrl: string; // Expecting actual logo URLs now
logoUrl: string;
};
type ClientLogosSectionProps = {
title: string;
description?: string;
clients: Client[];
/** Control animation speed (lower number = faster). Default: 40s */
speed?: number;
/** Size of the square background container in pixels. Default: 120 */
speed?: number; // pixels per frame
squareSize?: number;
};
const ClientLogosSection: React.FC<ClientLogosSectionProps> = ({
const ClientLogosSection = ({
title,
description,
clients = [], // Default to empty array
speed = 40, //Default speed in seconds for one full cycle
squareSize = 120, // Default size for the square container (e.g., 120px)
}) => {
// Need at least one client to render the marquee
if (clients.length === 0) {
return null; // Or render a placeholder message if preferred
}
// Duplicate the clients for a seamless loop effect
clients = [],
speed = 1,
squareSize = 120,
}: ClientLogosSectionProps) => {
const extendedClients = [...clients, ...clients];
const squareDim = `${squareSize}px`; // Convert size to string for inline style
const padding = Math.round(squareSize / 6); // Calculate padding based on size (adjust divisor as needed)
const squareDim = `${squareSize}px`;
const padding = Math.round(squareSize / 6);
const paddingDim = `${padding}px`;
const scrollRef = useRef<HTMLDivElement>(null);
const [direction, setDirection] = useState<"left" | "right">("left");
const [paused, setPaused] = useState(false);
let resumeTimeout: NodeJS.Timeout;
const pauseAndScroll = (dir: "left" | "right") => {
if (!scrollRef.current) return;
setPaused(true);
scrollRef.current.scrollBy({
left: dir === "left" ? -200 : 200,
behavior: "smooth",
});
clearTimeout(resumeTimeout);
resumeTimeout = setTimeout(() => {
setPaused(false);
}, 3000);
setDirection(dir === "left" ? "right" : "left");
};
useEffect(() => {
const container = scrollRef.current;
if (!container) return;
let animationFrame: number;
const step = () => {
if (!paused) {
if (direction === "left") {
container.scrollLeft += speed;
if (container.scrollLeft >= container.scrollWidth / 2) {
container.scrollLeft = 0;
}
} else {
container.scrollLeft -= speed;
if (container.scrollLeft <= 0) {
container.scrollLeft = container.scrollWidth / 2;
}
}
}
animationFrame = requestAnimationFrame(step);
};
animationFrame = requestAnimationFrame(step);
return () => cancelAnimationFrame(animationFrame);
}, [direction, paused, speed]);
// ✅ Safe early return after hooks
if (clients.length === 0) return null;
return (
<section className="py-16 md:py-20 bg-background overflow-hidden">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 text-center">
@ -45,19 +90,17 @@ const ClientLogosSection: React.FC<ClientLogosSectionProps> = ({
</h2>
</div>
{/* Marquee container - group allows pausing animation on hover */}
<div className="relative w-full overflow-hidden group">
{/* Inner container that will animate */}
{/* Logos Container */}
<div className="relative w-full overflow-hidden">
<div
className="flex flex-nowrap animate-marquee-continuous"
style={{ animationDuration: `${speed}s` }}
ref={scrollRef}
className="flex flex-nowrap overflow-x-hidden scrollbar-hide"
>
{extendedClients.map((client, index) => (
<div
key={`${client.name}-${index}`}
className="flex-shrink-0 mx-12 md:mx-16 py-4"
>
{/* Square Background Container */}
<div
title={client.name}
className="
@ -73,23 +116,35 @@ const ClientLogosSection: React.FC<ClientLogosSectionProps> = ({
style={{
width: squareDim,
height: squareDim,
padding: paddingDim, // Add padding inside the square
padding: paddingDim,
}}
>
<Image
src={client.logoUrl}
alt={`${client.name} Logo`}
layout="fill" // Let image fill the relative container
objectFit="contain" // Maintain aspect ratio within the container
fill
style={{ objectFit: "contain" }}
/>
</div>
</div>
))}
</div>
</div>
{/* Optional: Add fade effect at the edges */}
<div className="absolute inset-y-0 left-0 w-16 md:w-24 bg-gradient-to-r from-background to-transparent pointer-events-none z-10"></div>
<div className="absolute inset-y-0 right-0 w-16 md:w-24 bg-gradient-to-l from-background to-transparent pointer-events-none z-10"></div>
{/* Arrow Controls */}
<div className="flex justify-center mt-8 space-x-6">
<button
onClick={() => pauseAndScroll("right")}
className="px-4 py-2 rounded-full bg-muted hover:bg-muted/70 transition"
>
</button>
<button
onClick={() => pauseAndScroll("left")}
className="px-4 py-2 rounded-full bg-muted hover:bg-muted/70 transition"
>
</button>
</div>
{description && (
@ -103,6 +158,8 @@ const ClientLogosSection: React.FC<ClientLogosSectionProps> = ({
);
};
export default ClientLogosSection;
export const defaultClients: Client[] = [
{ name: "ABSA", logoUrl: "/images/absa.png" },
{ name: "SYBRIN", logoUrl: "/images/sybrin.svg" },
@ -113,4 +170,4 @@ export const defaultClients: Client[] = [
{ name: "TOYOTA", logoUrl: "/images/toyota-logo.png" },
];
export default ClientLogosSection;

View File

@ -15,6 +15,8 @@ import {
FaUserCheck,
FaProjectDiagram,
} from "react-icons/fa";
import { Metadata } from "next";
// const leadershipTeam = [
// {
@ -42,6 +44,23 @@ import {
// linkedinUrl: "#",
// },
// ];
export const metadata: Metadata = {
title: "About Us | Owethu Managed Services (OMS)",
description: "Learn about OMS, our mission, vision, and the values that drive us to deliver exceptional IT solutions and services.",
keywords: [
"Owethu Managed Services",
"About OMS",
"OMS",
"Black-owned ",
"Women-owned",
"Tech company",
"bank statement reader",
"fintech solutions" ,
],
}
const coreValues = [
{

View File

@ -12,6 +12,7 @@ import {
import { COLORS } from "@/constants"; // Using COLORS constant
import ContactForm from "@/components/ContactForm";
// Define the structure for FAQ items
interface FAQItem {
id: number;

View File

@ -29,6 +29,7 @@ export const metadata: Metadata = {
"fintech solutions",
"IT consulting",
"OMS",
"CVEvolve",
"Owethu Managed Services",
"Centurion",
"Gauteng",

View File

@ -14,6 +14,7 @@ import FeaturedProductSection, {
} from "./_components/FeaturedProductSection";
import { getHome } from "@/lib/query/home";
export default async function HomePage() {
// Explicitly type the data variable, assuming getHome returns HeroSectionType or null/undefined
const data = await getHome();

View File

@ -8,6 +8,7 @@ export const metadata: Metadata = {
description:
"Our recruitment portal is currently under development. Stay tuned for updates on career opportunities at Owethu Managed Services.",
robots: "noindex, nofollow", // Prevent indexing of the coming soon page
};
export default function RecruitmentPortalPage() {

View File

@ -25,6 +25,8 @@ export const metadata: Metadata = {
"IT resource augmentation",
"managed IT services",
"OBSE",
"CVEvolve",
"bank statement extractor",
"bank statement automation",
"fintech solutions",
"IT consulting",

View File

@ -11,6 +11,7 @@ export const metadata: Metadata = {
alternates: {
canonical: "/tech-talk",
},
openGraph: {
title: "OMS TechTalk | Insights & Innovation",
description: "Stay updated with tech insights from OMS.",

View File

@ -208,18 +208,16 @@
} /* Added longer delay */
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.animate-fade-in-up {
animation: fadeInUp 0.8s ease-out forwards;
opacity: 0; /* Start hidden */
.animate-marquee-continuous {
animation: marquee linear infinite;
}
.paused {
animation-play-state: paused !important;
}

View File

@ -20,6 +20,7 @@ export const metadata: Metadata = {
"Owethu Managed Services (OMS) provides expert IT solutions in Centurion & South Africa, including resource augmentation, project management, custom software development, and the OBSE financial analysis tool.", // Include Keywords, Location, USP
keywords: [
"Owethu Managed Services",
"OMS",
"OBSE",
"IT solutions South Africa",
"resource augmentation",
@ -28,6 +29,7 @@ export const metadata: Metadata = {
"OBSE",
"financial data analysis",
"IT services Centurion",
"digital transformation",
], // Add relevant keywords
alternates: {
canonical: "/", // Assuming this is the root URL

View File

@ -1,9 +1,10 @@
// components/Footer.tsx
import React from "react";
import Link from "next/link";
import { FaLinkedin, FaInstagram } from "react-icons/fa";
import { FaLinkedin, FaInstagram, FaYoutube } from "react-icons/fa";
import Image from "next/image";
const omsLogoUrl = "/oms-logo.svg"; // Ensure this exists in /public
// const omsLogoDarkUrl = "/oms-logo-dark.svg"; // Optional dark mode logo
@ -127,7 +128,7 @@ const Footer = () => {
{/* Social Icons - Use muted bright color, primary on hover */}
<div className="flex space-x-4 mt-4">
<a
href="https://linkedin.com"
href="https://www.linkedin.com/company/owethu-managed-services-oms"
target="_blank"
rel="noopener noreferrer"
className="text-[var(--oms-white)]/70 hover:text-primary transition-colors"
@ -135,8 +136,9 @@ const Footer = () => {
>
<FaLinkedin size={24} />
</a>
<a
href="https://instagram.com"
href="https://www.instagram.com/owethumanagedservices_oms"
target="_blank"
rel="noopener noreferrer"
className="text-[var(--oms-white)]/70 hover:text-primary transition-colors"
@ -144,6 +146,17 @@ const Footer = () => {
>
<FaInstagram size={24} />
</a>
<a
href="https://www.youtube.com/@OwethuManagedServices-africa"
target="_blank"
rel="noopener noreferrer"
className="text-[var(--oms-white)]/70 hover:text-primary transition-colors"
aria-label="YouTube"
>
<FaYoutube size={24} />
</a>
</div>
</div>
@ -223,6 +236,8 @@ const Footer = () => {
>
Privacy Policy
</Link> */}
<a
href="/Privacy-Policy/Recruitment-Privacy-Policy.pdf"
className="hover:text-primary transition-colors"
@ -236,6 +251,7 @@ const Footer = () => {
PAIA & POPIA
</a>
</div>
</div>
</div>
</footer>

View File

@ -13,6 +13,7 @@ import {
FiUsers,
FiCpu,
FiBox,
FiLayers
} from "react-icons/fi";
import ThemeToggle from "./ThemeToggle";
@ -265,15 +266,16 @@ const HeaderClient = () => {
</div>
</div>
</div>
{/* Products */}
<div
className={`group ${isActive("/obse") ? "active" : ""}`} // Add active class to group
className={`group ${isActive("/products") ? "active" : ""}`} // Add active class to group
onMouseEnter={handleProductsMouseEnter}
onMouseLeave={handleProductsMouseLeave}
>
<button
className={`${megaMenuTriggerClasses} ${
isActive("/obse") ? "after:w-full" : ""
isActive("/products") ? "after:w-full" : ""
}`}
>
{" "}
@ -283,7 +285,7 @@ const HeaderClient = () => {
</button>
<div
className={`
absolute left-0 top-full w-full shadow-lg z-1000
absolute left-0 top-full w-full shadow-lg z-40
bg-card border-x border-b border-border rounded-b-lg
opacity-0 invisible translate-y-[-10px]
${
@ -295,7 +297,7 @@ const HeaderClient = () => {
`}
>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-5">
<div className="max-w-md">
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 max-w-xl">
<Link
href="/obse"
className={`${megaMenuItemClasses} ${
@ -307,7 +309,44 @@ const HeaderClient = () => {
<FiBox className={megaMenuIconClasses} />
<div className={megaMenuTextWrapperClasses}>
<p className={megaMenuTitleClasses}>
OBSE Platform
</p>
</div>
</Link>
{/* Add more service links here
<Link
href="/services/project-management"
className={`${megaMenuItemClasses} ${
isActive("/services/project-management")
? "text-primary"
: ""
}`} // Apply active class
>
<FiBriefcase className={megaMenuIconClasses} />
<div className={megaMenuTextWrapperClasses}>
<p className={megaMenuTitleClasses}>
Project Management
</p>
</div>
</Link>
*/}
<Link
href="https://cvevolve.com/"
target="_blank"
className={`${megaMenuItemClasses} ${
isActive("/obse") ? "text-primary" : ""
}`}
>
{" "}
{/* Apply active class */}
<FiLayers className={megaMenuIconClasses} />
<div className={megaMenuTextWrapperClasses}>
<p className={megaMenuTitleClasses}>
CVEvolve
</p>
</div>
</Link>
@ -315,6 +354,8 @@ const HeaderClient = () => {
</div>
</div>
</div>
{/* Join Our Team */}
@ -425,6 +466,11 @@ const HeaderClient = () => {
OBSE
</DropdownLink>
{/* small screen investigation */}
<DropdownLink href="https://cvevolve.com" onClick={handleMobileLinkClick}>
CVEvolve
</DropdownLink>
{/* <span className="pt-3 pb-1 text-xs uppercase text-muted-foreground">
Join Us
</span>