Initial Commit

This commit is contained in:
libertyoms
2025-04-20 09:47:38 +02:00
parent da54bec6ea
commit 211cb3fc6f
14 changed files with 5514 additions and 163 deletions

View File

@ -0,0 +1,22 @@
// components/ChatbotWidget.tsx
import React from "react";
import { FaComments } from "react-icons/fa";
const ChatbotWidget = () => {
// TODO: Implement actual chat functionality (e.g., integrate a service)
const handleChatOpen = () => {
alert("Chatbot functionality to be implemented!");
};
return (
<button
// onClick={handleChatOpen}
className="fixed bottom-6 right-6 bg-primary text-dark p-4 rounded-full shadow-lg hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-all duration-300 z-50"
aria-label="Open Chat"
>
<FaComments size={24} />
</button>
);
};
export default ChatbotWidget;

170
components/Footer.tsx Normal file
View File

@ -0,0 +1,170 @@
// components/Footer.tsx
import React from "react";
import Link from "next/link";
import { FaLinkedin, FaInstagram } from "react-icons/fa"; // npm install react-icons
import Image from "next/image";
const omsLogoUrl = "/oms-logo.svg";
const Footer = () => {
return (
<footer className="bg-dark text-light pt-16 pb-8">
<div className="container mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
{/* About/Logo */}
<div>
<div className="flex items-center space-x-2 mb-4">
<Image
src={omsLogoUrl} // Use your actual logo path
alt="OMS Logo"
width={40} // Adjust size as needed
height={40}
priority // Load logo quickly
/>
<span className="text-xl font-bold text-light">OMS</span>
</div>
<p className="text-sm text-gray-400">Owethu Managed Services</p>
<p className="text-sm text-gray-400 mt-2">
Where innovation meets excellence.
</p>
</div>
{/* Quick Links */}
<div>
<h5 className="text-lg font-semibold mb-4 text-primary">
Quick Links
</h5>
<ul className="space-y-2">
<li>
<Link
href="/about"
className="hover:text-primary transition-colors duration-300"
>
About Us
</Link>
</li>
<li>
<Link
href="/services"
className="hover:text-primary transition-colors duration-300"
>
Services
</Link>
</li>
<li>
<Link
href="/products"
className="hover:text-primary transition-colors duration-300"
>
Products
</Link>
</li>
<li>
<Link
href="/join-us"
className="hover:text-primary transition-colors duration-300"
>
Join Our Team
</Link>
</li>
<li>
<Link
href="/contact"
className="hover:text-primary transition-colors duration-300"
>
Contact Us
</Link>
</li>
</ul>
</div>
{/* Contact Info */}
<div>
<h5 className="text-lg font-semibold mb-4 text-primary">Contact</h5>
<p className="text-sm text-gray-400 mb-2">
Unit 10 B Centuria Park
<br />
265 Von Willich Avenue
<br />
Die Hoewes, Centurion, 0159
</p>
<p className="text-sm text-gray-400 mb-2">Phone: (012) 051 3282</p>
<p className="text-sm text-gray-400 mb-2">
Email:{" "}
<a href="mailto:hello@oms.africa" className="hover:text-primary">
hello@oms.africa
</a>
</p>
<div className="flex space-x-4 mt-4">
<a
href="https://linkedin.com"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-primary"
>
<FaLinkedin size={24} />
</a>
<a
href="https://instagram.com"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-primary"
>
<FaInstagram size={24} />
</a>
</div>
</div>
{/* Newsletter */}
<div>
<h5 className="text-lg font-semibold mb-4 text-primary">
Newsletter
</h5>
<p className="text-sm text-gray-400 mb-3">
Stay updated with our latest news.
</p>
<form className="flex flex-col sm:flex-row gap-2">
<input
type="email"
placeholder="Enter your email"
className="flex-grow px-4 py-2 rounded-md bg-gray-700 text-light border border-gray-600 focus:outline-none focus:ring-2 focus:ring-primary/50"
/>
<button
type="submit"
className="bg-primary text-dark px-4 py-2 rounded-md font-semibold hover:bg-primary/90 transition-colors duration-300"
>
Subscribe
</button>
</form>
{/* TODO: Add Badges */}
<div className="mt-6 space-x-4">
<span className="inline-block bg-gray-600 px-3 py-1 rounded text-xs font-semibold">
Salesforce Partner
</span>
<span className="inline-block bg-gray-600 px-3 py-1 rounded text-xs font-semibold">
BBB-EE Level X
</span>
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-gray-700 pt-8 flex flex-col md:flex-row justify-between items-center text-sm text-gray-500">
<p>
© {new Date().getFullYear()} Owethu Managed Services. All Rights
Reserved.
</p>
<div className="flex space-x-4 mt-4 md:mt-0">
<Link href="/privacy-policy" className="hover:text-primary">
Privacy Policy
</Link>
<Link href="/paia-popia" className="hover:text-primary">
PAIA & POPIA
</Link>
</div>
</div>
</div>
</footer>
);
};
export default Footer;

314
components/Header.tsx Normal file
View File

@ -0,0 +1,314 @@
// components/Header.tsx
"use client";
import React, { useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
import {
FiChevronDown,
FiClipboard,
FiArrowRight,
FiMenu,
FiX,
} from "react-icons/fi";
import ThemeToggle from "./ThemeToggle";
const omsLogoUrl = "/oms-logo.svg";
// --- Basic Dropdown Placeholder ---
type DropdownMenuProps = {
trigger: React.ReactNode;
children: React.ReactNode;
menuClasses?: string;
};
const DropdownMenu = ({
trigger,
children,
menuClasses = "w-48",
}: DropdownMenuProps) => (
<div className="relative group">
<button className="flex items-center space-x-1 text-sm font-medium focus:outline-none inherit-color group">
{trigger}
<FiChevronDown className="w-4 h-4 transition-transform duration-200 group-hover:rotate-180" />
</button>
<div
className={`
absolute left-0 mt-2 ${menuClasses} origin-top-left rounded-md shadow-lg z-50
bg-light border border-gray-200 dark:bg-[var(--color-dark-bg-secondary)] dark:border-[var(--color-dark-border)]
opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200
`}
>
<div className="py-1">{children}</div>
</div>
</div>
);
type DropdownLinkProps = {
href: string;
children: React.ReactNode;
};
const DropdownLink = ({ href, children }: DropdownLinkProps) => (
<Link
href={href}
className="block px-4 py-2 text-sm text-gray-700 dark:text-[var(--color-dark-text-secondary)] hover:bg-gray-100 hover:text-primary dark:hover:bg-gray-700 dark:hover:text-primary"
>
{children}
</Link>
);
// --- End Dropdown Placeholder ---
const Header = () => {
const pathname = usePathname() || "/";
const isActive = (path: string) => pathname === path;
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggleMenu = () => setIsMenuOpen((open) => !open);
const handleMobileLinkClick = () => setIsMenuOpen(false);
// Optionally switch logo based on a global CSS class or via next-themes
const currentLogo = omsLogoUrl;
return (
<header className="sticky top-0 z-50 shadow-md bg-light dark:bg-dark-bg dark:border-b dark:border-dark-border">
{/* Top Row */}
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link
href="/"
className="flex items-center space-x-2"
title="OMS Home"
>
<Image
src={currentLogo}
alt="OMS Logo"
width={40}
height={40}
priority
/>
<span className="text-xl font-bold text-dark dark:text-light hidden sm:inline">
Owethu Managed Services
</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center space-x-6 lg:space-x-8">
<Link
href="/"
className={`text-sm font-medium ${
isActive("/")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text-secondary)] hover:text-primary"
}`}
>
Home
</Link>
<Link
href="/about"
className={`text-sm font-medium ${
pathname.startsWith("/about")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text-secondary)] hover:text-primary"
}`}
>
About Us
</Link>
<Link
href="/contact"
className={`text-sm font-medium ${
isActive("/contact")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text-secondary)] hover:text-primary"
}`}
>
Contact Us
</Link>
</nav>
{/* Right Utilities (Desktop) */}
<div className="hidden md:flex items-center space-x-4">
<ThemeToggle />
<Link
href="/request-demo"
className="flex items-center text-sm font-medium bg-primary text-dark px-3 py-1.5 rounded-md hover:bg-opacity-90 transition-colors"
title="Request a Demo"
>
<FiClipboard className="w-4 h-4 mr-1.5" />
Request Demo
</Link>
</div>
{/* Mobile Buttons */}
<div className="md:hidden flex items-center">
<ThemeToggle />
<button
onClick={toggleMenu}
className="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary focus:outline-none ml-3"
aria-label="Toggle menu"
>
{isMenuOpen ? (
<FiX className="w-6 h-6" />
) : (
<FiMenu className="w-6 h-6" />
)}
</button>
</div>
</div>
</div>
{/* Secondary Row */}
<div className="bg-primary dark:bg-primary">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="hidden md:flex justify-between items-center h-12">
<nav className="flex items-center space-x-6 lg:space-x-8 text-dark">
<DropdownMenu trigger={<span>Services</span>}>
<DropdownLink href="/services/resource-augmentation">
Resource Augmentation
</DropdownLink>
<DropdownLink href="/services/project-management">
Project Management
</DropdownLink>
<DropdownLink href="/services/product-development">
Product Development
</DropdownLink>
</DropdownMenu>
<DropdownMenu trigger={<span>Products</span>}>
<DropdownLink href="/products/obse">OBSE</DropdownLink>
</DropdownMenu>
<DropdownMenu trigger={<span>Join Our Team</span>}>
<DropdownLink href="/join-us/vacancies">Vacancies</DropdownLink>
<DropdownLink href="/join-us/portal">
Recruitment Portal
</DropdownLink>
</DropdownMenu>
</nav>
<Link
href="/services"
className="flex items-center text-sm font-medium text-dark hover:text-opacity-80 transition-opacity group"
>
Explore Our Offerings
<FiArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
</Link>
</div>
</div>
</div>
{/* Mobile Menu Panel */}
<div
id="mobile-menu"
className={`md:hidden absolute top-full left-0 w-full shadow-lg transition-all duration-300 ease-in-out overflow-hidden bg-light dark:bg-[var(--color-dark-bg-secondary)] border-t border-gray-200 dark:border-[var(--color-dark-border)] ${
isMenuOpen ? "max-h-screen py-4" : "max-h-0 py-0"
}`}
>
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 flex flex-col space-y-3">
<Link
href="/"
onClick={handleMobileLinkClick}
className={`block py-2 text-base font-medium ${
isActive("/")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
}`}
>
Home
</Link>
<Link
href="/about"
onClick={handleMobileLinkClick}
className={`block py-2 text-base font-medium ${
pathname.startsWith("/about")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
}`}
>
About Us
</Link>
<span className="pt-2 text-xs uppercase text-gray-500 dark:text-gray-400">
Services
</span>
<Link
href="/services/resource-augmentation"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
Resource Augmentation
</Link>
<Link
href="/services/project-management"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
Project Management
</Link>
<Link
href="/services/product-development"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
Product Development
</Link>
<span className="pt-2 text-xs uppercase text-gray-500 dark:text-gray-400">
Products
</span>
<Link
href="/products/obse"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
OBSE
</Link>
<span className="pt-2 text-xs uppercase text-gray-500 dark:text-gray-400">
Join Us
</span>
<Link
href="/join-us/vacancies"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
Vacancies
</Link>
<Link
href="/join-us/portal"
onClick={handleMobileLinkClick}
className="block py-1 pl-3 text-base font-medium text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
>
Recruitment Portal
</Link>
<Link
href="/contact"
onClick={handleMobileLinkClick}
className={`block py-2 text-base font-medium ${
isActive("/contact")
? "text-primary font-semibold"
: "text-gray-700 dark:text-[var(--color-dark-text)] hover:text-primary"
}`}
>
Contact Us
</Link>
<div className="pt-4">
<Link
href="/request-demo"
onClick={handleMobileLinkClick}
className="flex w-full justify-center items-center text-sm font-medium bg-primary text-dark px-4 py-2 rounded-md hover:bg-opacity-90 transition-colors"
title="Request a Demo"
>
<FiClipboard className="w-4 h-4 mr-1.5" />
Request Demo
</Link>
</div>
</nav>
</div>
</header>
);
};
export default Header;

197
components/HeaderSingle.tsx Normal file
View File

@ -0,0 +1,197 @@
// components/Header.tsx
import React from "react";
import Link from "next/link";
import Image from "next/image";
import { FiSearch, FiChevronDown, FiClipboard } from "react-icons/fi"; // Added ChevronDown for dropdowns and Clipboard for Demo
// TODO: Ensure this path is correct
const omsLogoUrl = "/oms-logo.svg"; // Or your actual logo file
// Placeholder for Dropdown Menu component (implement separately)
const DropdownMenu = ({
trigger,
children,
}: {
trigger: React.ReactNode;
children: React.ReactNode;
}) => {
// Basic structure - Needs state and proper styling for visibility toggle
return (
<div className="relative group">
<button className="flex items-center space-x-1 text-sm font-medium text-gray-700 hover:text-primary focus:outline-none">
{trigger}
<FiChevronDown className="w-4 h-4 transition-transform duration-200 group-hover:rotate-180" />
</button>
<div className="absolute left-0 mt-2 w-48 bg-light border border-gray-200 rounded-md shadow-lg opacity-0 group-hover:opacity-100 invisible group-hover:visible transition-all duration-200 z-50">
{children}
</div>
</div>
);
};
const Header = () => {
// In a real app use: import { usePathname } from 'next/navigation';
// const pathname = usePathname();
const pathname = "/"; // Placeholder for example
const isActive = (path: string) => pathname === path;
return (
<header className="sticky top-0 z-50 bg-light shadow-md">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex-shrink-0">
<Link
href="/"
className="flex items-center space-x-2"
title="Owethu Managed Services Home"
>
<Image
src={omsLogoUrl}
alt="OMS Logo"
width={40}
height={40}
priority
/>
<span className="text-xl font-bold text-dark hidden sm:inline">
Owethu Managed Services
</span>
</Link>
</div>
<nav className="hidden md:flex items-center space-x-6 lg:space-x-8">
<Link
href="/"
className={`text-sm font-medium ${
isActive("/")
? "text-primary font-semibold"
: "text-gray-700 hover:text-primary"
}`}
>
Home
</Link>
{/* About Us Dropdown */}
<DropdownMenu trigger={<span>About Us</span>}>
<Link
href="/about"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Company Overview
</Link>
<Link
href="/about#team"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Our Team
</Link>
{/* Add other sub-links if needed */}
</DropdownMenu>
{/* Services Dropdown */}
<DropdownMenu trigger={<span>Services</span>}>
<Link
href="/services/resource-augmentation"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Resource Augmentation
</Link>
<Link
href="/services/project-management"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Project Management
</Link>
<Link
href="/services/product-development"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Product Development
</Link>
</DropdownMenu>
{/* Products Dropdown */}
<DropdownMenu trigger={<span>Products</span>}>
<Link
href="/products/obse"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
OBSE
</Link>
{/* Add other products if any */}
</DropdownMenu>
{/* Join Our Team Dropdown */}
<DropdownMenu trigger={<span>Join Our Team</span>}>
<Link
href="/join-us/vacancies"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Vacancies
</Link>
<Link
href="/join-us/portal"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary"
>
Recruitment Portal
</Link>
</DropdownMenu>
<Link
href="/contact"
className={`text-sm font-medium ${
isActive("/contact")
? "text-primary font-semibold"
: "text-gray-700 hover:text-primary"
}`}
>
Contact Us
</Link>
</nav>
<div className="flex items-center space-x-4">
<button
className="text-gray-600 hover:text-primary focus:outline-none"
title="Search"
>
<FiSearch className="w-5 h-5" />
<span className="sr-only">Search</span>
</button>
<Link
href="/request-demo" // Link to a demo request page or section
className="flex items-center text-sm font-medium bg-primary text-dark px-3 py-1.5 rounded-md hover:bg-primary/90 transition-colors"
title="Request a Demo"
>
<FiClipboard className="w-4 h-4 mr-1.5" />
Request Demo
</Link>
</div>
<div className="md:hidden flex items-center">
<button className="text-gray-600 focus:outline-none">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16m-7 6h7"
></path>
</svg>
<span className="sr-only">Open menu</span>
</button>
{/* Mobile menu component would go here */}
</div>
</div>
</div>
</header>
);
};
export default Header;

View File

@ -0,0 +1,30 @@
"use client";
import { useTheme } from "next-themes";
import { DarkModeSwitch } from "react-toggle-dark-mode";
import { useState, useEffect } from "react";
const ThemeToggle = () => {
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
// avoid SSR mismatch
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
// placeholder box so layout doesnt jump
return <div style={{ width: 20, height: 20 }} />;
}
return (
<DarkModeSwitch
checked={resolvedTheme === "dark"}
onChange={(checked) => setTheme(checked ? "dark" : "light")}
size={20}
/>
);
};
export default ThemeToggle;

54
components/ui/Button.tsx Normal file
View File

@ -0,0 +1,54 @@
// components/ui/Button.tsx
import React from "react";
import Link from "next/link";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
href?: string;
variant?: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
children: React.ReactNode;
className?: string;
}
const Button: React.FC<ButtonProps> = ({
href,
variant = "primary",
size = "md",
children,
className = "",
...props
}) => {
const baseStyle =
"inline-flex items-center justify-center rounded-md font-semibold transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2";
const variantStyles = {
primary: "bg-primary text-dark hover:bg-primary/90 focus:ring-primary/50",
secondary: "bg-dark text-light hover:bg-gray-800 focus:ring-dark/50",
outline:
"border border-primary text-primary hover:bg-primary hover:text-dark focus:ring-primary/50",
};
const sizeStyles = {
sm: "px-4 py-2 text-sm",
md: "px-6 py-3 text-base",
lg: "px-8 py-4 text-lg",
};
const combinedClassName = `${baseStyle} ${variantStyles[variant]} ${sizeStyles[size]} ${className}`;
if (href) {
return (
<Link href={href} className={combinedClassName}>
{children}
</Link>
);
}
return (
<button className={combinedClassName} {...props}>
{children}
</button>
);
};
export default Button;