34 Commits

Author SHA1 Message Date
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
8d40a10e02 fix: updated tracking code 2025-06-11 16:54:02 +02:00
985c4b5a85 Merge branch 'dev' of https://github.com/OwethuManagedServices/oms-website-nextjs into dev 2025-06-11 16:42:18 +02:00
755eea55a4 feature: added umami 2025-06-11 16:42:02 +02:00
98f581b348 Merge pull request #31 from OwethuManagedServices/dev
Dev
2025-05-28 10:19:35 +02:00
8f828a374c Merge pull request #30 from OwethuManagedServices/25-05-2025/New-Changes
update contact phone number and button variant in CallToActionSection
2025-05-28 10:19:03 +02:00
45822f9af8 Merge pull request #29 from OwethuManagedServices/dev
Dev
2025-05-28 09:07:43 +02:00
3d5417852c Merge pull request #28 from OwethuManagedServices/25-05-2025/New-Changes
update default speed for client logos animation and modify footer con…
2025-05-28 09:06:01 +02:00
af917f3484 Merge pull request #26 from OwethuManagedServices/dev
Dev
2025-05-27 16:45:09 +02:00
0658c1ce28 Merge pull request #27 from OwethuManagedServices/25-05-2025/New-Changes
refactor newsletter subscription form layout for better responsiveness
2025-05-27 16:44:24 +02:00
7ba4ef1872 Merge pull request #25 from OwethuManagedServices/25-05-2025/New-Changes
update section component structure
2025-05-27 16:16:11 +02:00
2044f7207e Merge pull request #24 from OwethuManagedServices/dev
Dev
2025-05-27 15:45:27 +02:00
61ce1848d2 Merge pull request #23 from OwethuManagedServices/25-05-2025/New-Changes
knew changs
2025-05-27 15:44:59 +02:00
4f71f687d6 Merge pull request #22 from OwethuManagedServices/dev
Dev
2025-05-27 15:33:02 +02:00
91e2f34a63 Merge pull request #21 from OwethuManagedServices/25-05-2025/New-Changes
25-05-2025
2025-05-27 15:32:18 +02:00
78e74dfc7d Merge pull request #20 from OwethuManagedServices/dev
Dev
2025-05-27 02:57:45 +02:00
be8a2fe95d Merge pull request #19 from OwethuManagedServices/25-05-2025/New-Changes
new colors and some minor fixes
2025-05-27 02:57:03 +02:00
9949158d31 Merge pull request #18 from OwethuManagedServices/dev
Dev
2025-05-26 22:32:33 +02:00
6188a7ffbc Merge pull request #17 from OwethuManagedServices/25-05-2025/New-Changes
new changes
2025-05-26 22:32:00 +02:00
985f5c43ba Merge pull request #16 from OwethuManagedServices/dev
Dev
2025-05-26 16:42:30 +02:00
25691d6a2e Merge pull request #15 from OwethuManagedServices/25-05-2025/New-Changes
25-05-2025/For Oms cv evolve
2025-05-26 16:41:42 +02:00
6f3c946845 Merge pull request #14 from OwethuManagedServices/dev
Dev
2025-05-26 11:29:34 +02:00
b84b287dc1 Merge pull request #13 from OwethuManagedServices/25-05-2025/New-Changes
removed contact us page
2025-05-26 11:28:32 +02:00
b1f701e55d replaced URL 2025-05-26 10:30:55 +02:00
d1c497c936 Merge pull request #12 from OwethuManagedServices/dev
Dev
2025-05-26 10:02:29 +02:00
6d2a8c1a59 Merge pull request #11 from OwethuManagedServices/25-05-2025/New-Changes
26-05-2025/Fixed Used Variables and Icons
2025-05-26 10:01:36 +02:00
af744bd192 Merge pull request #10 from OwethuManagedServices/dev
Dev
2025-05-26 09:53:57 +02:00
2eb3e35cdb Merge pull request #9 from OwethuManagedServices/25-05-2025/New-Changes
25 05 2025/new changes
2025-05-26 09:53:06 +02:00
b6bbf9b54d Merge pull request #8 from OwethuManagedServices/25-05-2025/New-Changes
25 05 2025/new changes
2025-05-26 09:49:53 +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
13 changed files with 202 additions and 52 deletions

View File

@ -1,42 +1,85 @@
// 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
}
clients = [],
speed = 1,
squareSize = 120,
}: ClientLogosSectionProps) => {
if (clients.length === 0) return null;
// Duplicate the clients for a seamless loop effect
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);
// Scroll manually
scrollRef.current.scrollBy({
left: dir === "left" ? -200 : 200,
behavior: "smooth",
});
// Clear previous timeout and resume after 3 seconds
clearTimeout(resumeTimeout);
resumeTimeout = setTimeout(() => {
setPaused(false);
}, 3000);
// Set the direction for automatic scroll after pause
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]);
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 +88,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 +114,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 && (

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

@ -12,7 +12,7 @@ interface ExtendedVacancy extends Vacancy {
}
async function getVacancy(slug: string): Promise<ExtendedVacancy | null> {
const res = await fetch(`http://localhost:3000/api/vacancies/${slug}`, {
const res = await fetch(`${process.env.WEBSITE_URL}/api/vacancies/${slug}`, {
cache: "no-store",
});
if (!res.ok) {

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

@ -4,6 +4,7 @@ import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { ThemeProvider } from "@/providers/theme-provider";
import Script from "next/script";
const poppins = Poppins({
subsets: ["latin"],
@ -19,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",
@ -27,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
@ -95,6 +98,14 @@ export default function RootLayout({
<main>{children}</main>
<Footer />
</ThemeProvider>
<Script
src="https://umami.obse.africa/script.js"
data-website-id={
process.env.NEXT_PUBLIC_UMAMI_WEB_ID ||
"d9f0e4d7-0f0a-45e4-91bf-62e0e65e25d2"
}
strategy="afterInteractive"
/>
</body>
</html>
);

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>