Compare commits
76 Commits
06-05-2025
...
26e3d0975f
| Author | SHA1 | Date | |
|---|---|---|---|
| 26e3d0975f | |||
| 4e6d0a5512 | |||
| f0c68f549d | |||
| 2fade93bf7 | |||
| 33008c17f9 | |||
| 8e889d5e53 | |||
| 0530c9c943 | |||
| 8d40a10e02 | |||
| 985c4b5a85 | |||
| 755eea55a4 | |||
| 98f581b348 | |||
| 8f828a374c | |||
| 944066c42f | |||
| 45822f9af8 | |||
| 3d5417852c | |||
| 5c2714b202 | |||
| af917f3484 | |||
| 0658c1ce28 | |||
| ae6e2b020c | |||
| 7ba4ef1872 | |||
| b908926912 | |||
| 2044f7207e | |||
| 61ce1848d2 | |||
| db391c833a | |||
| 4f71f687d6 | |||
| 91e2f34a63 | |||
| 91081f2f9d | |||
| 78e74dfc7d | |||
| be8a2fe95d | |||
| 860a895e45 | |||
| 9949158d31 | |||
| 6188a7ffbc | |||
| 735f98f564 | |||
| 985f5c43ba | |||
| 25691d6a2e | |||
| 5baa62e86d | |||
| 6f3c946845 | |||
| b84b287dc1 | |||
| 54b3b73657 | |||
| b1f701e55d | |||
| d1c497c936 | |||
| 6d2a8c1a59 | |||
| feac643754 | |||
| af744bd192 | |||
| 2eb3e35cdb | |||
| 782e887ff8 | |||
| 9c14197f0c | |||
| b6bbf9b54d | |||
| e9c8d25eb6 | |||
| b19c24efc1 | |||
| d2281b9648 | |||
| 62192ab8fb | |||
| 7175ef9821 | |||
| d93d3348f3 | |||
| 3f702a7592 | |||
| 0f3eaff970 | |||
| ec3f4a51dc | |||
| e2347ecc87 | |||
| 225fbc707e | |||
| 730dc51629 | |||
| a61c545a3a | |||
| 53ce8dddc7 | |||
| 02e0a35ce8 | |||
| 5d9d830cb1 | |||
| 4119a2d3ef | |||
| 41d2db9383 | |||
| 3cb95c74cb | |||
| e198158c6d | |||
| 179f1c9a2c | |||
| 3bc7452434 | |||
| 77260b1f7a | |||
| 9b0cf2a5f6 | |||
| b1680d297b | |||
| d0b0d10124 | |||
| 1be00d7a42 | |||
| 809f5c6ff7 |
74
actions/apply.ts
Normal file
@ -0,0 +1,74 @@
|
||||
"use server";
|
||||
|
||||
import * as z from "zod";
|
||||
|
||||
// Re-define or import the schema to validate on the server-side
|
||||
// Ensure this matches the client-side schema
|
||||
const applicationSchema = z.object({
|
||||
vacancyId: z.string(),
|
||||
vacancyTitle: z.string(),
|
||||
firstName: z.string().min(1, "First name is required"),
|
||||
lastName: z.string().min(1, "Last name is required"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
phone: z.string().optional(),
|
||||
linkedinUrl: z.string().url("Invalid URL").optional().or(z.literal("")),
|
||||
portfolioUrl: z.string().url("Invalid URL").optional().or(z.literal("")),
|
||||
coverLetter: z.string().optional(),
|
||||
// Note: File uploads (resume) are not handled in this basic action.
|
||||
// Handling files requires FormData and different processing.
|
||||
});
|
||||
|
||||
type ApplicationFormData = z.infer<typeof applicationSchema>;
|
||||
|
||||
interface ActionResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export async function submitApplication(
|
||||
formData: ApplicationFormData
|
||||
): Promise<ActionResult> {
|
||||
// 1. Validate data on the server
|
||||
const validatedFields = applicationSchema.safeParse(formData);
|
||||
|
||||
if (!validatedFields.success) {
|
||||
console.error(
|
||||
"Server-side validation failed:",
|
||||
validatedFields.error.flatten().fieldErrors
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: "Invalid data provided. Please check the form.",
|
||||
// Optionally return specific field errors: errors: validatedFields.error.flatten().fieldErrors
|
||||
};
|
||||
}
|
||||
|
||||
const applicationData = validatedFields.data;
|
||||
|
||||
// 2. Process the application (e.g., save to database, send email)
|
||||
// For this demo, we'll just log the data.
|
||||
console.log(
|
||||
"Received application:",
|
||||
JSON.stringify(applicationData, null, 2)
|
||||
);
|
||||
|
||||
// Simulate processing time
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// In a real application:
|
||||
// - Save applicationData to your database (e.g., using Prisma or Directus SDK)
|
||||
// - Handle resume file upload (requires FormData, potentially upload to storage like S3/Minio)
|
||||
// - Send notification emails (to HR, to the applicant)
|
||||
|
||||
// Example of error handling during processing:
|
||||
// try {
|
||||
// await saveApplicationToDatabase(applicationData);
|
||||
// await sendConfirmationEmail(applicationData.email);
|
||||
// } catch (error) {
|
||||
// console.error('Failed to process application:', error);
|
||||
// return { success: false, message: 'Failed to save application.' };
|
||||
// }
|
||||
|
||||
// 3. Return success response
|
||||
return { success: true, message: "Application submitted successfully!" };
|
||||
}
|
||||
@ -17,7 +17,7 @@ const CallToActionSection: React.FC<CallToActionSectionProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
// Use primary background, primary-foreground for text
|
||||
<section className="bg-primary text-primary-foreground py-16 md:py-20">
|
||||
<section className="bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-primary-foreground py-16 md:py-20">
|
||||
{" "}
|
||||
{/* Adjusted padding */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
@ -30,7 +30,7 @@ const CallToActionSection: React.FC<CallToActionSectionProps> = ({
|
||||
</p>{" "}
|
||||
{/* Slightly less emphasis */}
|
||||
{/* Button needs contrast on primary bg. Use a secondary/outline/custom variant */}
|
||||
<Button href={buttonHref} variant="secondary" size="lg">
|
||||
<Button href={buttonHref} variant="black" size="lg" >
|
||||
{/* Example: Using 'secondary' which uses light/dark gray bg defined in globals */}
|
||||
{/* OR custom class: className="bg-background text-foreground hover:bg-background/90" */}
|
||||
{buttonText}
|
||||
|
||||
@ -1,84 +1,173 @@
|
||||
// components/ClientLogosSection.tsx
|
||||
import React from "react";
|
||||
import Image from "next/image"; // For actual logos
|
||||
import { FaBuilding, FaCar, FaLaptopCode, FaUsers } from "react-icons/fa"; // For placeholders
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
// Define structure for client data (adapt as needed)
|
||||
type Client = {
|
||||
name: string;
|
||||
logoUrl?: string; // URL to actual logo image
|
||||
icon?: React.ElementType; // Placeholder icon component
|
||||
logoUrl: string;
|
||||
};
|
||||
|
||||
type ClientLogosSectionProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
clients: Client[];
|
||||
speed?: number; // pixels per frame
|
||||
squareSize?: number;
|
||||
};
|
||||
|
||||
const ClientLogosSection: React.FC<ClientLogosSectionProps> = ({
|
||||
const ClientLogosSection = ({
|
||||
title,
|
||||
description,
|
||||
clients,
|
||||
}) => {
|
||||
clients = [],
|
||||
speed = 1,
|
||||
squareSize = 120,
|
||||
}: ClientLogosSectionProps) => {
|
||||
const extendedClients = [...clients, ...clients];
|
||||
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 (
|
||||
// Use semantic background
|
||||
<section className="py-16 md:py-20 bg-background">
|
||||
<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">
|
||||
{" "}
|
||||
{/* Use container */}
|
||||
{/* Use semantic foreground */}
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-12 text-foreground">
|
||||
{title}
|
||||
</h2>
|
||||
{/* TODO: Implement Auto-Sliding Panel (e.g., using Swiper.js or Embla Carousel) */}
|
||||
<div className="flex flex-wrap justify-center items-center gap-10 md:gap-16 opacity-70 dark:opacity-60">
|
||||
{" "}
|
||||
{/* Adjust opacity */}
|
||||
{clients.map((client, index) => (
|
||||
</div>
|
||||
|
||||
{/* Logos Container */}
|
||||
<div className="relative w-full overflow-hidden">
|
||||
<div
|
||||
key={index}
|
||||
title={client.name}
|
||||
className="transition-opacity hover:opacity-100"
|
||||
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"
|
||||
>
|
||||
<div
|
||||
title={client.name}
|
||||
className="
|
||||
relative flex items-center justify-center
|
||||
bg-muted/30 dark:bg-muted/20
|
||||
rounded-lg shadow-sm
|
||||
overflow-hidden
|
||||
grayscale hover:grayscale-0
|
||||
opacity-70 hover:opacity-100
|
||||
transition-all duration-300 ease-in-out
|
||||
cursor-pointer
|
||||
"
|
||||
style={{
|
||||
width: squareDim,
|
||||
height: squareDim,
|
||||
padding: paddingDim,
|
||||
}}
|
||||
>
|
||||
{client.logoUrl ? (
|
||||
<Image
|
||||
src={client.logoUrl}
|
||||
alt={`${client.name} Logo`}
|
||||
width={120} // Adjust size as needed
|
||||
height={40} // Adjust size as needed
|
||||
objectFit="contain"
|
||||
className="dark:invert dark:filter" // Example: Invert logo colors in dark mode if needed
|
||||
fill
|
||||
style={{ objectFit: "contain" }}
|
||||
/>
|
||||
) : client.icon ? (
|
||||
// Use semantic muted foreground for icons, primary on hover
|
||||
React.createElement(client.icon, {
|
||||
className:
|
||||
"text-5xl text-muted-foreground/80 hover:text-primary transition-colors",
|
||||
})
|
||||
) : (
|
||||
<span className="text-muted-foreground">{client.name}</span> // Fallback text
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</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 && (
|
||||
<p className="mt-8 text-muted-foreground italic text-sm">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<p className="mt-10 text-muted-foreground italic text-sm">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
// Example default data matching the original page (using placeholders)
|
||||
export default ClientLogosSection;
|
||||
|
||||
export const defaultClients: Client[] = [
|
||||
{ name: "Financial Services Client", icon: FaBuilding },
|
||||
{ name: "Automotive Client", icon: FaCar },
|
||||
{ name: "Tech Industry Client", icon: FaLaptopCode },
|
||||
{ name: "Generic Client 1", icon: FaUsers },
|
||||
{ name: "Generic Client 2", icon: FaBuilding },
|
||||
{ name: "ABSA", logoUrl: "/images/absa.png" },
|
||||
{ name: "SYBRIN", logoUrl: "/images/sybrin.svg" },
|
||||
{ name: "SASOL", logoUrl: "/images/sasol.png" },
|
||||
{ name: "JACARANDA", logoUrl: "/images/jacaranda.png" },
|
||||
{ name: "SALESFORCE", logoUrl: "/images/salesforce.png" },
|
||||
{ name: "BCX", logoUrl: "/images/bcx.png" },
|
||||
{ name: "TOYOTA", logoUrl: "/images/toyota-logo.png" },
|
||||
];
|
||||
|
||||
export default ClientLogosSection;
|
||||
|
||||
|
||||
@ -61,9 +61,9 @@ export const defaultCoreServices: ServiceItem[] = [
|
||||
},
|
||||
{
|
||||
icon: FaProjectDiagram,
|
||||
title: "Project Management",
|
||||
title: "IT Implementation",
|
||||
description:
|
||||
"Expert management ensuring on-time, within-budget delivery with superior results, risk mitigation, and maximum efficiency.",
|
||||
"Empowering your business through seamless IT rollouts—delivered on time and on budget. We turn complex systems into powerful solutions that drive transformation, reduce risk, and unlock real results. ",
|
||||
},
|
||||
{
|
||||
icon: FaCode,
|
||||
|
||||
@ -16,15 +16,15 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
buttonText,
|
||||
buttonHref,
|
||||
imageUrl = "/hero-bg.jpg", // Default background image
|
||||
//buttonHref,
|
||||
imageUrl = "/hero-bg-2.jpg", // Default background image
|
||||
}) => {
|
||||
return (
|
||||
<section className="relative h-[70vh] md:h-[85vh] flex items-center justify-center text-center bg-gradient-to-b from-black/10 to-black/40 text-white overflow-hidden">
|
||||
<section className="relative h-[70vh] md:h-[85vh] flex items-center justify-center text-center text-white overflow-hidden">
|
||||
{" "}
|
||||
{/* Adjusted background */}
|
||||
{/* Adjusted background bg-gradient-to-b from-black/10 to-black/40*/}
|
||||
{/* Background Image/Video */}
|
||||
<div className="absolute inset-0 z-0 opacity-40 dark:opacity-30">
|
||||
<div className="absolute inset-0 z-0 opacity-100 dark:opacity-30">
|
||||
{" "}
|
||||
{/* Adjusted opacity */}
|
||||
{imageUrl && (
|
||||
@ -52,7 +52,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||
{subtitle}
|
||||
</p>
|
||||
<Button
|
||||
href={buttonHref}
|
||||
href="/obse"
|
||||
variant="primary" // Use primary variant defined in Button component
|
||||
size="lg"
|
||||
className="animate-fade-in-up animation-delay-600"
|
||||
|
||||
@ -17,7 +17,7 @@ const HeroSectionDynamic: React.FC<HeroSectionProps> = ({
|
||||
subtitle,
|
||||
buttonText,
|
||||
buttonHref,
|
||||
imageUrl = "/hero-bg.jpg", // Ensure this high-quality image exists
|
||||
imageUrl = "/hero-bg-2.jpg", // Ensure this high-quality image exists
|
||||
}) => {
|
||||
return (
|
||||
<section className="relative flex items-center bg-background min-h-screen overflow-hidden">
|
||||
|
||||
@ -17,10 +17,11 @@ const HeroSectionModern: React.FC<HeroSectionProps> = ({
|
||||
subtitle,
|
||||
buttonText,
|
||||
buttonHref,
|
||||
imageUrl = "/hero-bg.jpg", // Default background image - MAKE SURE THIS EXISTS
|
||||
imageUrl = "/hero-bg-2.jpg", // Default background image - MAKE SURE THIS EXISTS
|
||||
}) => {
|
||||
return (
|
||||
// Use min-h-screen for full viewport height adjust if needed
|
||||
//bg-[linear-gradient(to_right,#f0e18a,#f9f4c3,#ecd973)]
|
||||
<section className="relative flex flex-col md:flex-row items-center bg-background min-h-[80vh] md:min-h-screen overflow-hidden">
|
||||
{/* Background Image/Video Layer */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
|
||||
@ -15,64 +15,83 @@ import {
|
||||
FaUserCheck,
|
||||
FaProjectDiagram,
|
||||
} from "react-icons/fa";
|
||||
import { Metadata } from "next";
|
||||
|
||||
|
||||
// const leadershipTeam = [
|
||||
// {
|
||||
// id: 1,
|
||||
// name: "Michael Shapiro",
|
||||
// title: "Managing Director",
|
||||
// imageUrl: "/images/profile1.jpg",
|
||||
// bio: "A seasoned leader and innovator with over 25 years of experience, driving strategic growth and fostering a culture of excellence within OMS.",
|
||||
// linkedinUrl: "#",
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// name: "Gareth Corbishley",
|
||||
// title: "Director",
|
||||
// imageUrl: "/images/profile2.jpg",
|
||||
// bio: "Expert in operational efficiency and technological implementation, ensuring seamless project delivery and client satisfaction.",
|
||||
// linkedinUrl: "#",
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// name: "Darryl Govender",
|
||||
// title: "Director",
|
||||
// imageUrl: "/images/profile3.jpg",
|
||||
// bio: "Specializes in aligning cutting-edge technology solutions with complex business needs to unlock transformative results.",
|
||||
// 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 leadershipTeam = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Michael Shapiro",
|
||||
title: "Managing Director",
|
||||
imageUrl: "/images/profile1.jpg",
|
||||
bio: "A seasoned leader and innovator with over 25 years of experience, driving strategic growth and fostering a culture of excellence within OMS.",
|
||||
linkedinUrl: "#",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Gareth Corbishley",
|
||||
title: "Director",
|
||||
imageUrl: "/images/profile2.jpg",
|
||||
bio: "Expert in operational efficiency and technological implementation, ensuring seamless project delivery and client satisfaction.",
|
||||
linkedinUrl: "#",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Darryl Govender",
|
||||
title: "Director",
|
||||
imageUrl: "/images/profile3.jpg",
|
||||
bio: "Specializes in aligning cutting-edge technology solutions with complex business needs to unlock transformative results.",
|
||||
linkedinUrl: "#",
|
||||
},
|
||||
];
|
||||
|
||||
const coreValues = [
|
||||
{
|
||||
icon: FaCheckCircle,
|
||||
title: "Service",
|
||||
description:
|
||||
"Dedicated to providing outstanding service at every touchpoint, meeting the highest standards, and creating lasting impact for our clients.",
|
||||
"We strive to elevate every experience — from client interactions to internal collaborations. With unwavering dedication, we deliver excellence that inspires growth, fosters partnerships, and creates a lasting, positive impact on everyone we touch.",
|
||||
},
|
||||
{
|
||||
icon: FaHandshake,
|
||||
title: "Accountability",
|
||||
description:
|
||||
"Taking ownership of our actions and delivering on promises with transparency. Our commitment fosters trust and ensures we consistently meet client expectations.",
|
||||
"We embrace ownership with pride, holding ourselves and each other to the highest standards. Our transparency and commitment to our promises build the trust that strengthens relationships, propels progress, and empowers everyone to thrive.",
|
||||
},
|
||||
{
|
||||
icon: FaHeart,
|
||||
title: "Passion",
|
||||
description:
|
||||
"Driven by a passion for innovation, we embrace new ideas and technologies, constantly seeking ways to improve and deliver creative, boundary-pushing solutions.",
|
||||
"Driven by a relentless passion for innovation, we embrace challenges as opportunities. We inspire creativity, ignite new ideas, and fuel transformation — always seeking to push the limits of what’s possible and make a difference in everything we do.",
|
||||
},
|
||||
{
|
||||
icon: FaComments,
|
||||
title: "Communication",
|
||||
description:
|
||||
"Clear, consistent, and proactive communication is central. We ensure all stakeholders are aligned, informed, and empowered, fostering collaboration.",
|
||||
"Open, honest, and proactive communication is the bridge that connects us all. We believe in the power of dialogue to align, inspire, and drive collaboration — ensuring that every voice is heard and every idea has the opportunity to flourish.",
|
||||
},
|
||||
{
|
||||
icon: FaShieldAlt,
|
||||
title: "Trust",
|
||||
description:
|
||||
"Cultivating trust through ethical practices, honesty, and transparency. We uphold the highest integrity for long-term, mutually beneficial relationships.",
|
||||
"At the heart of our work lies unwavering trust. Built on integrity, honesty, and respect, we nurture strong, meaningful relationships that create a foundation for long-term success — together, as partners, as a team, and as a community.",
|
||||
},
|
||||
];
|
||||
|
||||
@ -125,22 +144,23 @@ const industryExpertise = [
|
||||
{
|
||||
icon: FaBriefcase,
|
||||
industry: "Retail/E-commerce",
|
||||
details: "Describe experience in this sector.",
|
||||
details:
|
||||
"Experience in building scalable e-commerce platforms, optimizing digital customer journeys, enabling secure payment integrations, and leveraging data analytics to drive personalized shopping experiences and operational efficiency.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutUsPage() {
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden">
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200">
|
||||
{" "}
|
||||
{/* Prevent horizontal scroll */}
|
||||
{/* Prevent horizontal scroll & Add dark mode base */}
|
||||
{/* Section 1: Hero / Company Overview */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800 text-white py-24 md:py-40">
|
||||
{/* Optional decorative background elements */}
|
||||
<div className="absolute inset-0 bg-black opacity-30"></div>
|
||||
<div className="absolute inset-0 bg-black opacity-30 dark:opacity-50"></div>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-10"
|
||||
style={{ backgroundImage: "url('/path/to/abstract-tech-bg.jpg')" }}
|
||||
style={{ backgroundImage: "url('/path/to/abstract-tech-bg.jpg')"}}
|
||||
></div>{" "}
|
||||
{/* Add a subtle background image */}
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
@ -150,7 +170,7 @@ export default function AboutUsPage() {
|
||||
>
|
||||
About Owethu Managed Services
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-200 font-poppins">
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 font-poppins">
|
||||
Your strategic partner in navigating the digital frontier. We fuse
|
||||
deep technical expertise with a passion for innovation to craft
|
||||
bespoke IT solutions that drive tangible business results and unlock
|
||||
@ -159,35 +179,39 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 2: Our Genesis & Purpose */}
|
||||
<section className="py-16 md:py-24 bg-gray-50">
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 leading-tight">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
Forged in Innovation, <br className="hidden md:inline" /> Driven
|
||||
by Purpose
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 leading-relaxed mb-4 font-poppins">
|
||||
Expand on the founding story/motivation. E.g., Owethu Managed
|
||||
Services was born from a clear vision: to harness the
|
||||
transformative power of technology not just to solve problems,
|
||||
but to proactively create opportunities for businesses to
|
||||
thrive. We saw a need for a partner who truly understands both
|
||||
technology's potential and the unique challenges modern
|
||||
organizations face.
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4 font-poppins">
|
||||
Owethu Managed Services wasn’t built in a boardroom — it was
|
||||
born from a deeper calling: a desire to do more than simply
|
||||
exist in the tech space. We set out to use innovation not as a
|
||||
buzzword, but as a lifeline — pulling businesses out of
|
||||
stagnation and into a future full of possibility.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-700 leading-relaxed font-poppins">
|
||||
Elaborate on the journey. E.g., Our journey has been defined by
|
||||
a relentless pursuit of knowledge, adaptation to the
|
||||
ever-evolving tech landscape, and an unwavering commitment to
|
||||
client success. This focus has established us as trusted
|
||||
advisors and thought leaders in digital transformation.
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4 font-poppins">
|
||||
From day one, we’ve challenged the status quo. We don’t just fix
|
||||
problems — we anticipate them, reimagine them, and turn them
|
||||
into powerful stepping stones for growth. Our mission is simple:
|
||||
break what seems impossible, and rebuild it into something
|
||||
extraordinary.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-poppins">
|
||||
Today, OMS is more than a service provider. We are trusted
|
||||
digital transformation partners, creative problem-solvers, and
|
||||
passionate believers in human-driven innovation. This isn’t just
|
||||
business. This is our purpose.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative h-64 md:h-80 rounded-lg overflow-hidden shadow-lg">
|
||||
{/* Replace with a relevant, high-quality image representing innovation or teamwork */}
|
||||
<Image
|
||||
src="/images/team-collaborative.png"
|
||||
src="/about-2.jpg" // Ensure this image exists in your public folder
|
||||
alt="Team collaborating on innovative solutions"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
@ -198,12 +222,12 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 3: Vision & Mission (Similar layout, refined look) */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white">
|
||||
Our North Star
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-600 mt-3 max-w-2xl mx-auto font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-600 dark:text-gray-400 mt-3 max-w-2xl mx-auto font-poppins">
|
||||
Guiding our strategy, actions, and partnerships.
|
||||
</p>
|
||||
</div>
|
||||
@ -211,56 +235,51 @@ export default function AboutUsPage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-12">
|
||||
{/* Vision Card */}
|
||||
<div
|
||||
className="bg-gray-50 p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300 border-t-4 border-gold-500 flex flex-col items-center text-center"
|
||||
className="bg-gray-50 dark:bg-gray-800 p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300 border-t-4 border-gold-500 flex flex-col items-center text-center"
|
||||
style={{ borderColor: "#e1c44a" }}
|
||||
>
|
||||
<FaBullseye
|
||||
className="text-5xl mb-5 text-gold-500"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h3 className="text-2xl font-bold mb-4 font-poppins text-gray-900">
|
||||
<h3 className="text-2xl font-bold mb-4 font-poppins text-gray-900 dark:text-white">
|
||||
Our Vision
|
||||
</h3>
|
||||
<p className="text-gray-700 leading-relaxed font-poppins flex-grow">
|
||||
<p className="text-gray-700 dark:text-gray-300 leading-relaxed font-poppins flex-grow">
|
||||
"To be global leaders in delivering cutting-edge IT
|
||||
solutions, pushing the boundaries of what's possible, and
|
||||
transforming industries for the better. We aim to empower
|
||||
organisations to break through technological barriers and
|
||||
achieve greater success."
|
||||
solutions, pushing the boundaries of what's possible,and
|
||||
transforming industries for the better."
|
||||
</p>
|
||||
</div>
|
||||
{/* Mission Card */}
|
||||
<div
|
||||
className="bg-gray-50 p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300 border-t-4 border-gold-500 flex flex-col items-center text-center"
|
||||
className="bg-gray-50 dark:bg-gray-800 p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300 border-t-4 border-gold-500 flex flex-col items-center text-center"
|
||||
style={{ borderColor: "#e1c44a" }}
|
||||
>
|
||||
<FaHandshake
|
||||
className="text-5xl mb-5 text-gold-500"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h3 className="text-2xl font-bold mb-4 font-poppins text-gray-900">
|
||||
Our Mission
|
||||
<h3 className="text-2xl font-bold mb-4 font-poppins text-gray-900 dark:text-white">
|
||||
Our Purpose
|
||||
</h3>
|
||||
<p className="text-gray-700 leading-relaxed font-poppins flex-grow">
|
||||
"We are dedicated to creating tailored, innovative
|
||||
solutions that drive business success. By combining expertise
|
||||
with cutting-edge technology, we solve complex problems and
|
||||
enable our clients to achieve outstanding results. We partner
|
||||
closely with clients to ensure every solution aligns with their
|
||||
unique needs for long-term success."
|
||||
<p className="text-gray-700 dark:text-gray-300 leading-relaxed font-poppins flex-grow">
|
||||
"Our purpose is to help our clients drive transformative
|
||||
growth and innovation to propel their business forward –
|
||||
redefining whats possible."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 4: Our Approach & Methodology */}
|
||||
<section className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200">
|
||||
<section className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 mb-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
How We Deliver Excellence
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 max-w-3xl mx-auto font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
Our methodology is built on collaboration, precision, and a
|
||||
relentless focus on delivering impactful results. We tailor our
|
||||
process to fit your specific project needs.
|
||||
@ -270,16 +289,16 @@ export default function AboutUsPage() {
|
||||
{ourApproach.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-2 hover:shadow-xl"
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-2 hover:shadow-xl dark:hover:shadow-gold-500/20"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl text-gold-500 mx-auto mb-5"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-3 font-poppins">
|
||||
<h4 className="text-xl font-semibold mb-3 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 font-poppins text-sm leading-relaxed">
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
@ -288,7 +307,7 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 5: Industry Expertise */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12 items-center">
|
||||
<div className="lg:col-span-1">
|
||||
@ -296,10 +315,10 @@ export default function AboutUsPage() {
|
||||
className="text-5xl text-gold-500 mb-4"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-gray-900 leading-tight">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
Deep Domain Knowledge
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 leading-relaxed font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-poppins">
|
||||
We combine broad technological capabilities with specialized
|
||||
expertise across key industries, understanding the unique
|
||||
challenges and opportunities within each sector.
|
||||
@ -309,13 +328,13 @@ export default function AboutUsPage() {
|
||||
{industryExpertise.map((exp) => (
|
||||
<div
|
||||
key={exp.industry}
|
||||
className="bg-gray-50 p-6 rounded-lg shadow-sm border-l-4 border-gold-500"
|
||||
className="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm border-l-4 border-gold-500"
|
||||
style={{ borderColor: "#e1c44a" }}
|
||||
>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins">
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{exp.industry}
|
||||
</h4>
|
||||
<p className="text-gray-600 font-poppins text-sm leading-relaxed">
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
{exp.details}
|
||||
</p>
|
||||
</div>
|
||||
@ -324,6 +343,7 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 6: Technology Philosophy */}
|
||||
<section className="py-16 md:py-24 bg-gray-800 text-white">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
@ -331,7 +351,7 @@ export default function AboutUsPage() {
|
||||
className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-white">
|
||||
Smart Technology, Applied Wisely
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-300 mb-8 font-poppins">
|
||||
@ -339,38 +359,38 @@ export default function AboutUsPage() {
|
||||
strategically. Our focus is on building solutions that are:
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
|
||||
<div className="bg-gray-700 p-6 rounded-lg">
|
||||
<div className="bg-gray-700 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h4
|
||||
className="text-xl font-semibold mb-2 font-poppins text-gold-500"
|
||||
style={{ color: "#e1c44a" }}
|
||||
>
|
||||
Scalable & Future-Proof
|
||||
</h4>
|
||||
<p className="text-gray-300 font-poppins text-sm">
|
||||
<p className="text-gray-300 dark:text-gray-300 font-poppins text-sm">
|
||||
Designed to grow with your business and adapt to future
|
||||
technological advancements.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-700 p-6 rounded-lg">
|
||||
<div className="bg-gray-700 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h4
|
||||
className="text-xl font-semibold mb-2 font-poppins text-gold-500"
|
||||
style={{ color: "#e1c44a" }}
|
||||
>
|
||||
Robust & Reliable
|
||||
</h4>
|
||||
<p className="text-gray-300 font-poppins text-sm">
|
||||
<p className="text-gray-300 dark:text-gray-300 font-poppins text-sm">
|
||||
Built with quality and security at the core, ensuring dependable
|
||||
performance.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-700 p-6 rounded-lg">
|
||||
<div className="bg-gray-700 dark:bg-gray-700 p-6 rounded-lg">
|
||||
<h4
|
||||
className="text-xl font-semibold mb-2 font-poppins text-gold-500"
|
||||
style={{ color: "#e1c44a" }}
|
||||
>
|
||||
Best-Fit Solutions
|
||||
</h4>
|
||||
<p className="text-gray-300 font-poppins text-sm">
|
||||
<p className="text-gray-300 dark:text-gray-300 font-poppins text-sm">
|
||||
Chosen based on your specific needs, budget, and existing
|
||||
infrastructure, not just the latest hype.
|
||||
</p>
|
||||
@ -378,26 +398,28 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Section 7: Core Values (Reusing previous structure, adjusting background) */}
|
||||
<section className="py-16 md:py-24 bg-gray-100">
|
||||
<section className="py-16 md:py-24 bg-gray-100 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-poppins text-gray-900">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 font-poppins text-gray-900 dark:text-white">
|
||||
Our Foundational Values
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{coreValues.map((value) => (
|
||||
<div
|
||||
key={value.title}
|
||||
className="bg-white p-6 rounded-lg shadow-sm text-center transition-shadow duration-300 hover:shadow-lg"
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-sm text-center transition-shadow duration-300 hover:shadow-lg dark:hover:shadow-gold-500/20"
|
||||
>
|
||||
<value.icon
|
||||
className="text-4xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins">
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins dark:text-white">
|
||||
{value.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 font-poppins text-sm leading-relaxed">
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
{value.description}
|
||||
</p>
|
||||
</div>
|
||||
@ -405,14 +427,16 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Section 8: The OMS Partnership */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div className="relative h-64 md:h-80 rounded-lg overflow-hidden shadow-lg order-last md:order-first">
|
||||
{/* Replace with an image representing partnership or client collaboration */}
|
||||
<Image
|
||||
src="/images/partners.jpg"
|
||||
src="/images/meeting.svg"
|
||||
alt="Client partnership and collaboration"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
@ -424,17 +448,17 @@ export default function AboutUsPage() {
|
||||
className="text-5xl text-gold-500 mb-4"
|
||||
style={{ color: "#e1c44a" }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 leading-tight">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
More Than Vendors, <br className="hidden md:inline" />{" "}
|
||||
We're Partners
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 leading-relaxed mb-4 font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4 font-poppins">
|
||||
We believe the best results come from true collaboration. We
|
||||
invest time in understanding your culture, goals, and
|
||||
invest time in understanding your culture, processes, goals, and
|
||||
challenges, working alongside your team as an extension of your
|
||||
own capabilities.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-700 leading-relaxed font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-poppins">
|
||||
This means open communication, shared goals, proactive
|
||||
problem-solving, and a long-term commitment to your success,
|
||||
extending far beyond project completion.
|
||||
@ -443,14 +467,14 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 9: Our Leadership Team */}
|
||||
<section className="py-16 md:py-24 bg-gray-50">
|
||||
{/* Section 9: Our Leadership Team
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 mb-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Meet Our Leadership
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 max-w-3xl mx-auto font-poppins">
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
Slightly enhance the intro. E.g., Guided by experience and a
|
||||
passion for innovation, our leadership team fosters a culture of
|
||||
excellence and empowers our experts to deliver outstanding results
|
||||
@ -459,25 +483,22 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10 justify-center">
|
||||
{/* Added justify-center */}
|
||||
{leadershipTeam.map((member) => (
|
||||
<div
|
||||
key={member.id}
|
||||
className="bg-white rounded-lg shadow-lg overflow-hidden text-center transition-transform duration-300 hover:scale-105 max-w-sm mx-auto"
|
||||
className="bg-white dark:bg-gray-700 rounded-lg shadow-lg overflow-hidden text-center transition-transform duration-300 hover:scale-105 max-w-sm mx-auto dark:hover:shadow-gold-500/20"
|
||||
>
|
||||
{/* Added max-width and mx-auto for centering if fewer than 3 items */}
|
||||
<div className="relative h-60 w-full">
|
||||
{/* Slightly taller image */}
|
||||
<Image
|
||||
src={member.imageUrl || "/images/profile1.jpg"}
|
||||
alt={`Photo of ${member.name}`}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="bg-gray-200"
|
||||
className="bg-gray-200 dark:bg-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h4 className="text-xl font-semibold font-poppins mb-1">
|
||||
<h4 className="text-xl font-semibold font-poppins mb-1 dark:text-white">
|
||||
{member.name}
|
||||
</h4>
|
||||
<p
|
||||
@ -486,7 +507,7 @@ export default function AboutUsPage() {
|
||||
>
|
||||
{member.title}
|
||||
</p>
|
||||
<p className="text-gray-600 font-poppins text-sm mb-4">
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm mb-4">
|
||||
{member.bio}
|
||||
</p>
|
||||
{member.linkedinUrl && member.linkedinUrl !== "#" && (
|
||||
@ -494,10 +515,9 @@ export default function AboutUsPage() {
|
||||
href={member.linkedinUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-800 transition-colors duration-200 font-poppins"
|
||||
className="text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 transition-colors duration-200 font-poppins"
|
||||
aria-label={`LinkedIn profile of ${member.name}`}
|
||||
>
|
||||
{/* Using a simple text link for cleanliness */}
|
||||
Connect on LinkedIn
|
||||
</a>
|
||||
)}
|
||||
@ -505,31 +525,32 @@ export default function AboutUsPage() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Button removed as requested */}
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
{/* Section 10: Commitment to Impact (Optional, but adds value) */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-gold-500 text-gray-900"
|
||||
className="py-16 md:py-24 bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900 dark:bg-gold-600 dark:text-gray-900"
|
||||
style={{ backgroundColor: "#e1c44a" }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaChartLine className="text-5xl mx-auto mb-5" />
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
<FaChartLine className="text-5xl mx-auto mb-5 text-gray-800" />
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-gray-900">
|
||||
Driving Measurable Impact
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed font-poppins">
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed font-poppins text-gray-800">
|
||||
Ultimately, our success is measured by yours. We are committed to
|
||||
delivering solutions that not only meet technical requirements but
|
||||
also drive efficiency, foster growth, enhance user experiences, and
|
||||
provide a clear return on investment. Let's build a successful
|
||||
future, together.
|
||||
creating solutions that ignite momentum, unlock potential, and turn
|
||||
ambition into achievement — empowering you to rise above challenges
|
||||
and lead with confidence into the future. Together, let's build
|
||||
a future where your success is not just a possibility, but a
|
||||
reality.
|
||||
</p>
|
||||
{/* Optional CTA to contact */}
|
||||
<div className="mt-10">
|
||||
<a
|
||||
href="/contact"
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 transition-colors duration-300 font-poppins"
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300 font-poppins"
|
||||
>
|
||||
Start the Conversation
|
||||
</a>
|
||||
|
||||
@ -1,48 +1,74 @@
|
||||
import React from "react";
|
||||
import type { Metadata } from "next";
|
||||
import ContactForm from "@/components/ContactForm";
|
||||
import { FiMapPin, FiPhone, FiMail, FiClock } from "react-icons/fi"; // Import icons
|
||||
"use client"; // Needed for FAQ state
|
||||
|
||||
// SEO Metadata for the Contact page
|
||||
export const metadata: Metadata = {
|
||||
title: "Contact Us | Owethu Managed Services (OMS)",
|
||||
description:
|
||||
"Get in touch with Owethu Managed Services. Contact us for IT solutions, resource augmentation, project management, and custom software development inquiries.",
|
||||
alternates: {
|
||||
canonical: "/contact",
|
||||
},
|
||||
openGraph: {
|
||||
title: "Contact OMS",
|
||||
description: "Reach out to OMS for expert IT services and solutions.",
|
||||
url: "https://oms.africa/contact", // Replace with your actual domain
|
||||
images: [
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
FaMapMarkerAlt,
|
||||
FaPhone,
|
||||
FaEnvelope,
|
||||
FaClock,
|
||||
FaChevronDown,
|
||||
FaChevronUp,
|
||||
} from "react-icons/fa"; // Using Fa icons for consistency
|
||||
import { COLORS } from "@/constants"; // Using COLORS constant
|
||||
import ContactForm from "@/components/ContactForm";
|
||||
|
||||
|
||||
// Define the structure for FAQ items
|
||||
interface FAQItem {
|
||||
id: number;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
const faqData: FAQItem[] = [
|
||||
{
|
||||
url: "/og-image-contact.jpg", // Create a specific OG image for contact
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Contact Owethu Managed Services",
|
||||
id: 1,
|
||||
question: "What services does Owethu Managed Services offer?",
|
||||
answer:
|
||||
"We offer a comprehensive range of IT services including custom software development, resource augmentation (IT staffing), project management, cloud solutions, system integration, and IT consulting tailored to various industries.",
|
||||
},
|
||||
],
|
||||
{
|
||||
id: 2,
|
||||
question: "Which industries do you specialize in?",
|
||||
answer:
|
||||
"We have deep expertise in Financial Services & Fintech, Automotive, Technology, and Retail/E-commerce sectors, understanding the unique challenges and opportunities within each.",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Contact OMS",
|
||||
description: "Get in touch with Owethu Managed Services.",
|
||||
images: ["/og-image-contact.jpg"],
|
||||
{
|
||||
id: 3,
|
||||
question: "How can I request a quote for a project?",
|
||||
answer:
|
||||
"You can request a quote by filling out the contact form on this page with details about your project requirements, calling us directly, or sending an email to hello@oms.africa. We'll get back to you promptly to discuss your needs.",
|
||||
},
|
||||
};
|
||||
{
|
||||
id: 4,
|
||||
question: "What are your business hours?",
|
||||
answer:
|
||||
"Our standard business hours are Monday to Friday, 8:00 AM to 5:00 PM South African Standard Time (SAST). We are closed on weekends and public holidays.",
|
||||
},
|
||||
];
|
||||
|
||||
// Contact Page Component
|
||||
export default function ContactPage() {
|
||||
const [openFaqId, setOpenFaqId] = useState<number | null>(null);
|
||||
|
||||
const toggleFaq = (id: number) => {
|
||||
setOpenFaqId(openFaqId === id ? null : id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-background to-secondary/50 text-foreground">
|
||||
{/* Hero Section */}
|
||||
<section className="py-20 md:py-28 text-center bg-primary/10 dark:bg-primary/5 border-b border-border">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4 text-primary">
|
||||
// Added dark mode base styles
|
||||
<div className="bg-gray-50 text-gray-800 dark:bg-gray-900 dark:text-gray-200 overflow-x-hidden font-poppins">
|
||||
{/* Hero Section - Adjusted padding for mobile */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800 text-white py-16 md:py-28">
|
||||
<div className="absolute inset-0 bg-black opacity-40 dark:opacity-50"></div>
|
||||
<div className="container mx-auto px-4 md:px-6 text-center relative z-10">
|
||||
<h1
|
||||
className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md dark:text-gold-400" // Use primary color directly or a dark-mode friendly version
|
||||
style={{ color: COLORS.primary }} // Keep gold color for heading, ensure contrast in dark mode
|
||||
>
|
||||
Get In Touch
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
<p className="text-base sm:text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 font-poppins">
|
||||
We're here to help! Whether you have a question about our
|
||||
services, need assistance, or want to discuss a project, reach out
|
||||
and let us know.
|
||||
@ -50,23 +76,31 @@ export default function ContactPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content Area (Form + Info) */}
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 md:gap-16 lg:gap-20 items-start">
|
||||
{/* Contact Information Section */}
|
||||
<div className="space-y-8 bg-card p-8 rounded-lg border border-border shadow-sm">
|
||||
<h2 className="text-3xl font-semibold mb-6 text-foreground">
|
||||
{/* Main Content Area (Form + Info) - Adjusted padding, added dark mode */}
|
||||
<section className="py-12 md:py-24 bg-white dark:bg-gray-800">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
{/* Grid stacks vertically by default, becomes 2 columns on large screens */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-16 items-start">
|
||||
{/* Contact Information Section - Added dark mode styles */}
|
||||
<div
|
||||
className="bg-gray-50 dark:bg-gray-700 p-6 md:p-8 rounded-xl shadow-md border-t-4 border-gold-500 dark:border-gold-400 space-y-6 md:space-y-8"
|
||||
style={{ borderColor: COLORS.primary }} // Keep primary border color
|
||||
>
|
||||
<h2 className="text-2xl md:text-3xl font-bold font-poppins text-gray-900 dark:text-white mb-6">
|
||||
Contact Information
|
||||
</h2>
|
||||
|
||||
<div className="flex items-start space-x-4">
|
||||
<FiMapPin className="w-6 h-6 text-primary mt-1 flex-shrink-0" />
|
||||
<FaMapMarkerAlt
|
||||
className="w-6 h-6 text-gold-500 mt-1 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Our Office
|
||||
</h3>
|
||||
<address className="text-muted-foreground not-italic text-sm leading-relaxed">
|
||||
<address className="text-gray-600 dark:text-gray-300 font-poppins not-italic text-sm leading-relaxed">
|
||||
{/* ... existing address lines ... */}
|
||||
Unit 10 B Centuria Park
|
||||
<br />
|
||||
265 Von Willich Avenue
|
||||
@ -75,12 +109,12 @@ export default function ContactPage() {
|
||||
<br />
|
||||
South Africa
|
||||
</address>
|
||||
{/* Optional: Link to Google Maps */}
|
||||
<a
|
||||
href="https://www.google.com/maps/place/Owethu+Managed+Services/@-25.863168,28.186075,17z/data=!3m1!4b1!4m6!3m5!1s0x1e9565b7018f5f9b:0x4d8a4ae3a2c0d9a1!8m2!3d-25.8631728!4d28.1886499!16s%2Fg%2F11h1_q_1_f?entry=ttu" // Replace with your actual Google Maps link
|
||||
href="https://www.google.com/maps/place/Owethu+Managed+Services/@-25.863168,28.186075,17z/data=!3m1!4b1!4m6!3m5!1s0x1e9565b7018f5f9b:0x4d8a4ae3a2c0d9a1!8m2!3d-25.8631728!4d28.1886499!16s%2Fg%2F11h1_q_1_f?entry=ttu"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary hover:underline mt-2 inline-block"
|
||||
className="text-sm font-medium font-poppins hover:underline mt-2 inline-block dark:text-gold-400 dark:hover:text-gold-300"
|
||||
style={{ color: COLORS.primary }} // Keep primary link color
|
||||
>
|
||||
View on Google Maps
|
||||
</a>
|
||||
@ -88,25 +122,35 @@ export default function ContactPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4">
|
||||
<FiPhone className="w-6 h-6 text-primary mt-1 flex-shrink-0" />
|
||||
<FaPhone
|
||||
className="w-5 h-5 text-gold-500 mt-1 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-foreground">Phone</h3>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Phone
|
||||
</h3>
|
||||
<a
|
||||
href="tel:+27120513282"
|
||||
className="text-muted-foreground hover:text-primary transition text-sm"
|
||||
href="tel:+27684855721"
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm font-poppins"
|
||||
>
|
||||
(012) 051 3282
|
||||
+27 68 485 5721
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4">
|
||||
<FiMail className="w-6 h-6 text-primary mt-1 flex-shrink-0" />
|
||||
<FaEnvelope
|
||||
className="w-5 h-5 text-gold-500 mt-1 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-foreground">Email</h3>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Email
|
||||
</h3>
|
||||
<a
|
||||
href="mailto:hello@oms.africa"
|
||||
className="text-muted-foreground hover:text-primary transition text-sm"
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm font-poppins"
|
||||
>
|
||||
hello@oms.africa
|
||||
</a>
|
||||
@ -114,38 +158,109 @@ export default function ContactPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4">
|
||||
<FiClock className="w-6 h-6 text-primary mt-1 flex-shrink-0" />
|
||||
<FaClock
|
||||
className="w-5 h-5 text-gold-500 mt-1 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-foreground">
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Business Hours
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm font-poppins">
|
||||
Monday - Friday: 8:00 AM - 5:00 PM (SAST)
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm font-poppins">
|
||||
Weekends & Public Holidays: Closed
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Form Section */}
|
||||
<div className="bg-card p-8 rounded-lg border border-border shadow-sm">
|
||||
<h2 className="text-3xl font-semibold mb-6 text-foreground">
|
||||
{/* Contact Form Section - Added dark mode styles */}
|
||||
<div
|
||||
className="bg-gray-50 dark:bg-gray-700 p-6 md:p-8 rounded-xl shadow-md border-t-4 border-gold-500 dark:border-gold-400"
|
||||
style={{ borderColor: COLORS.primary }} // Keep primary border color
|
||||
>
|
||||
<h2 className="text-2xl md:text-3xl font-bold font-poppins text-gray-900 dark:text-white mb-6">
|
||||
Send Us a Message
|
||||
</h2>
|
||||
{/* Assuming ContactForm handles its own dark mode or inherits text colors */}
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Optional: Map Section Placeholder */}
|
||||
{/* <section className="h-96 bg-muted border-t border-border">
|
||||
<div className="container mx-auto h-full flex items-center justify-center">
|
||||
<p className="text-muted-foreground">[Embedded Map Placeholder - e.g., Google Maps iframe]</p>
|
||||
{/* Map Section - Adjusted padding, added dark mode, responsive height */}
|
||||
<section className="py-12 md:py-24 bg-gray-100 dark:bg-gray-900">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-8 text-center">
|
||||
Find Us Here
|
||||
</h2>
|
||||
{/* Adjusted height for different screen sizes */}
|
||||
<div className="aspect-w-16 aspect-h-9 h-64 sm:h-80 md:h-96 mx-auto rounded-lg overflow-hidden shadow-lg border border-gray-200 dark:border-gray-700">
|
||||
<iframe
|
||||
// ... existing iframe attributes ...
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1313.49241475678!2d28.192648350415848!3d-25.85177958049519!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x1e95643aa4e82f9f%3A0xe4052722532cd30f!2sCenturia%20Park!5e1!3m2!1sen!2sza!4v1745745054858!5m2!1sen!2sza"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen={true}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
title="Owethu Managed Services Location"
|
||||
></iframe>
|
||||
</div>
|
||||
</section> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section - Adjusted padding, added dark mode */}
|
||||
<section className="py-12 md:py-24 bg-white dark:bg-gray-800">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-10 md:mb-12 text-center">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<div className="max-w-5xl mx-auto space-y-4">
|
||||
{faqData.map((faq) => (
|
||||
<div
|
||||
key={faq.id}
|
||||
className="border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden shadow-sm bg-white dark:bg-gray-700"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleFaq(faq.id)}
|
||||
className="flex justify-between items-center w-full p-4 text-left font-semibold font-poppins text-gray-800 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gold-500 dark:focus:ring-gold-400 focus:ring-opacity-50"
|
||||
aria-expanded={openFaqId === faq.id}
|
||||
aria-controls={`faq-answer-${faq.id}`}
|
||||
>
|
||||
<span className="text-sm md:text-base">{faq.question}</span>
|
||||
{openFaqId === faq.id ? (
|
||||
<FaChevronUp
|
||||
className="w-5 h-5 text-gold-500 flex-shrink-0 ml-2" // Added flex-shrink-0 and margin
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
) : (
|
||||
<FaChevronDown
|
||||
className="w-5 h-5 text-gold-500 flex-shrink-0 ml-2" // Added flex-shrink-0 and margin
|
||||
style={{ color: COLORS.primary }} // Keep primary icon color
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
<div
|
||||
id={`faq-answer-${faq.id}`}
|
||||
// Adjusted padding and text size
|
||||
className={`px-4 pb-4 pt-2 text-gray-600 dark:text-gray-300 font-poppins text-xs sm:text-sm leading-relaxed transition-all duration-300 ease-in-out ${
|
||||
openFaqId === faq.id ? "block" : "hidden"
|
||||
}`}
|
||||
role="region"
|
||||
aria-labelledby={`faq-question-${faq.id}`}
|
||||
>
|
||||
{faq.answer}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
959
app/(website)/obse/page-backup.tsx
Normal file
@ -0,0 +1,959 @@
|
||||
// // app/obse/page.tsx
|
||||
|
||||
// import Image from "next/image";
|
||||
// import Link from "next/link"; // Import Link for internal navigation
|
||||
// import {
|
||||
// FaBolt, // Represents speed, automation
|
||||
// FaExclamationTriangle, // Represents challenges, risks
|
||||
// FaLightbulb, // Represents solution, intelligence
|
||||
// FaCogs, // Represents how it works, engine
|
||||
// FaThumbsUp, // Represents benefits, advantages
|
||||
// FaChartPie, // Represents data outputs, insights
|
||||
// FaMicrochip, // Represents technology
|
||||
// FaBriefcase, // Represents use cases
|
||||
// FaUsersCog, // Represents customization, partnership
|
||||
// FaInfoCircle, // Represents about section (alternative)
|
||||
// FaPhoneAlt, // Represents contact
|
||||
// FaPlayCircle, // Optional for mini-CTA
|
||||
// FaArrowRight, // For CTAs or steps
|
||||
// FaFilePdf, // For PDF/Statement icon
|
||||
// FaSearchDollar, // For income detection
|
||||
// FaShieldAlt, // For fraud detection
|
||||
// FaNetworkWired, // For API integration
|
||||
// FaTable, // For data structuring
|
||||
// FaBrain, // For Machine Learning
|
||||
// FaBusinessTime, // For turnaround time
|
||||
// FaHourglassHalf, // For reducing time
|
||||
// FaHandHoldingUsd, // For operational costs
|
||||
// FaUserCheck, // For accuracy
|
||||
// FaChartLine, // For efficiency/profitability
|
||||
// FaClipboardList, // For transaction summary
|
||||
// FaCalendarAlt, // For debit orders
|
||||
// FaMoneyBillWave, // For disposable income
|
||||
// FaBalanceScale, // For affordability
|
||||
// FaExchangeAlt, // For Income/Expenses
|
||||
// FaEye, // For review/interact step
|
||||
// FaUpload, // For upload step
|
||||
// FaPaperPlane,
|
||||
// FaCheckCircle, // For access/integrate step
|
||||
// } from "react-icons/fa";
|
||||
|
||||
// const keyFeatures = [
|
||||
// {
|
||||
// icon: FaBolt,
|
||||
// title: "Automated Data Extraction",
|
||||
// description:
|
||||
// "Processes statements in seconds (avg. 9-12s), drastically reducing manual effort.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaUserCheck, // Using accuracy icon here
|
||||
// title: "Advanced OCR Technology",
|
||||
// description:
|
||||
// "High accuracy, reads varied fonts, handles stamps, and distinguishes similar characters ('7' vs 'Z').",
|
||||
// },
|
||||
// {
|
||||
// icon: FaFilePdf,
|
||||
// title: "Comprehensive SA Bank Coverage",
|
||||
// description:
|
||||
// "Handles statements from all major SA banks and various formats (PDF, scanned, stamped, multi/single page).",
|
||||
// },
|
||||
// {
|
||||
// icon: FaSearchDollar,
|
||||
// title: "Intelligent Income Detection",
|
||||
// description:
|
||||
// "Auto-identifies salaried/non-salaried income with suggestive logic and exclusionary rules.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaShieldAlt,
|
||||
// title: "Enhanced Fraud Detection",
|
||||
// description:
|
||||
// "Detects document tampering, fraudulent transaction insertion, and developing ML-based behavioral profiling.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaCogs, // Using cogs for interaction/processing
|
||||
// title: "Dynamic Data Interaction",
|
||||
// description:
|
||||
// "User-friendly interface for real-time transaction recategorization and immediate recalculations.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaNetworkWired,
|
||||
// title: "Seamless API Integration",
|
||||
// description:
|
||||
// "Push structured, validated data directly into your credit scoring, LOS, or other internal systems.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaChartPie,
|
||||
// title: "Visual Analytics & Reporting",
|
||||
// description:
|
||||
// "Intuitive dashboards showing cash flow, income sources, and spending patterns.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaTable, // Using table for dashboard data
|
||||
// title: "MI Dashboard & Performance Tracking",
|
||||
// description:
|
||||
// "Monitor processing volumes, success rates, processing times, and user performance.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaTable,
|
||||
// title: "Multiple Export Formats",
|
||||
// description:
|
||||
// "Download results as structured Excel, formatted PDFs, or copy data for easy transfer.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaCheckCircle, // Reusing from About Us for validation
|
||||
// title: "Built-in Accuracy Checks",
|
||||
// description:
|
||||
// "Includes automated validations and data reviews for uncertain extractions to ensure reliability.",
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const coreBenefits = [
|
||||
// {
|
||||
// icon: FaHourglassHalf,
|
||||
// title: "Dramatically Reduce Processing Time",
|
||||
// description:
|
||||
// "Cut down statement analysis from hours to mere minutes/seconds.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaUserCheck,
|
||||
// title: "Ensure High Accuracy & Reliability",
|
||||
// description:
|
||||
// "Minimize human error with advanced OCR and built-in validation rules.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaHandHoldingUsd,
|
||||
// title: "Lower Operational Costs",
|
||||
// description:
|
||||
// "Reduce reliance on manual data entry staff and associated overheads.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaShieldAlt,
|
||||
// title: "Strengthen Fraud Prevention",
|
||||
// description:
|
||||
// "Proactively identify tampered documents and suspicious financial activity.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaBusinessTime,
|
||||
// title: "Accelerate Turnaround Times",
|
||||
// description:
|
||||
// "Speed up loan applications, credit assessments, and customer onboarding.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaBrain, // Using brain for better decisions
|
||||
// title: "Improve Decision-Making",
|
||||
// description:
|
||||
// "Base assessments on accurate, comprehensive, and rapidly available data.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaChartLine,
|
||||
// title: "Enhance Operational Efficiency",
|
||||
// description: "Streamline workflows, free up staff for higher-value tasks.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaThumbsUp, // Using thumbs up for compliance
|
||||
// title: "Improve Compliance",
|
||||
// description:
|
||||
// "Ensure consistent and accurate data handling for regulatory requirements.",
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const dataOutputs = [
|
||||
// {
|
||||
// icon: FaClipboardList,
|
||||
// title: "Basic Transaction Summary",
|
||||
// description:
|
||||
// "Concise overview of transactions for spending pattern analysis.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaCalendarAlt,
|
||||
// title: "Debit Order Analysis",
|
||||
// description: "Clear tracking of recurring payments and regular expenses.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaMoneyBillWave,
|
||||
// title: "Disposable Income Calculation",
|
||||
// description: "Assessment of available funds after essential expenses.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaBalanceScale,
|
||||
// title: "Affordability Assessment Data",
|
||||
// description: "Key data points to support financial capability evaluation.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaExchangeAlt,
|
||||
// title: "Income & Expenses Breakdown",
|
||||
// description: "Detailed insights into financial inflows and outflows.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaTable,
|
||||
// title: "Detailed Transaction List",
|
||||
// description:
|
||||
// "Full, structured transaction data exportable for deep analysis.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaExclamationTriangle,
|
||||
// title: "Fraud Indicators",
|
||||
// description:
|
||||
// "Flags for potentially suspicious documents or transaction patterns.",
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const useCases = [
|
||||
// { title: "Lending & Credit Origination (Personal, VAF, SME)" },
|
||||
// { title: "Credit Risk Assessment & Scoring Input" },
|
||||
// { title: "Automated Income Verification" },
|
||||
// { title: "Affordability Calculations & Compliance Checks" },
|
||||
// { title: "Faster Customer Onboarding (KYC/Financial)" },
|
||||
// { title: "Portfolio Financial Health Monitoring" },
|
||||
// { title: "Internal Audit & Reconciliation Support" },
|
||||
// { title: "Fraud Investigation Data Preparation" },
|
||||
// ];
|
||||
|
||||
// export default function ObsePage() {
|
||||
// return (
|
||||
// <>
|
||||
// <section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-900 text-white py-24 md:py-40">
|
||||
// <div className="absolute inset-0 bg-black opacity-40 dark:opacity-60"></div>
|
||||
// <div className="absolute inset-0 bg-cover bg-center opacity-10 dark:opacity-5"></div>
|
||||
// <div className="container mx-auto px-6 text-center relative z-10">
|
||||
// <div className="mb-6 inline-block bg-gold-500/10 p-3 rounded-lg border border-gold-500/30">
|
||||
// <Image
|
||||
// src="/images/oms-logo-light.png"
|
||||
// alt="OMS Logo"
|
||||
// width={80}
|
||||
// height={40}
|
||||
// className="h-10 w-auto"
|
||||
// />
|
||||
// </div>
|
||||
// <h1 className="text-4xl md:text-6xl font-bold mb-4 font-poppins text-gold-500 drop-shadow-md">
|
||||
// Revolutionize Lending with OBSE
|
||||
// </h1>
|
||||
// <p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 font-poppins mb-8">
|
||||
// Automate data extraction, enhance accuracy, detect fraud, and
|
||||
// accelerate decision-making with South Africa's intelligent bank
|
||||
// statement solution: Owethu Bank Statement Extraction.
|
||||
// </p>
|
||||
// <div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
// <Link
|
||||
// href="#contact"
|
||||
// className="inline-block bg-gold-500 text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 dark:hover:bg-gold-400 transition-colors duration-300 font-poppins"
|
||||
// >
|
||||
// Request a Demo <FaArrowRight className="inline ml-2" />
|
||||
// </Link>
|
||||
// <Link
|
||||
// href="#how-it-works"
|
||||
// className="inline-block bg-transparent border-2 border-gold-500 text-gold-500 font-bold py-3 px-8 rounded-md hover:bg-gold-500/10 transition-colors duration-300 font-poppins"
|
||||
// >
|
||||
// <FaPlayCircle className="inline mr-2" /> See How It Works
|
||||
// </Link>
|
||||
// </div>
|
||||
// <p className="text-sm text-gray-400 mt-6 font-poppins">
|
||||
// Keywords: Bank Statement Extraction, OCR, Automation, Financial Data
|
||||
// Analysis, South Africa
|
||||
// </p>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// <section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
// <div>
|
||||
// <FaExclamationTriangle
|
||||
// className="text-5xl text-gold-500 mb-4"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
// The High Cost of Manual Bank Statement Processing
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-6 font-poppins">
|
||||
// In the rapidly evolving landscape of financial services, where
|
||||
// precision and speed are paramount, extracting data from bank
|
||||
// statements manually creates significant roadblocks. Efficiently
|
||||
// parsing financial information is critical for accurate risk
|
||||
// assessment, yet reliance on manual methods impacts efficiency
|
||||
// and decision-making.
|
||||
// </p>
|
||||
// <ul className="space-y-4 font-poppins">
|
||||
// <li className="flex items-start">
|
||||
// <FaHourglassHalf
|
||||
// className="text-gold-500 mt-1 mr-3 flex-shrink-0"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <span className="text-gray-700 dark:text-gray-300">
|
||||
// <strong className="dark:text-white">
|
||||
// Slow & Labor-Intensive:
|
||||
// </strong>{" "}
|
||||
// Countless hours spent on manual data entry lead to
|
||||
// operational bottlenecks.
|
||||
// </span>
|
||||
// </li>
|
||||
// <li className="flex items-start">
|
||||
// <FaUserCheck className="text-red-500 mt-1 mr-3 flex-shrink-0" />{" "}
|
||||
// {/* Using red for error */}
|
||||
// <span className="text-gray-700 dark:text-gray-300">
|
||||
// <strong className="dark:text-white">
|
||||
// Prone to Human Error:
|
||||
// </strong>{" "}
|
||||
// Mistakes risk inaccurate assessments, compliance breaches,
|
||||
// and poor decisions.
|
||||
// </span>
|
||||
// </li>
|
||||
// <li className="flex items-start">
|
||||
// <FaHandHoldingUsd
|
||||
// className="text-gold-500 mt-1 mr-3 flex-shrink-0"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <span className="text-gray-700 dark:text-gray-300">
|
||||
// <strong className="dark:text-white">
|
||||
// High Operational Costs:
|
||||
// </strong>{" "}
|
||||
// Significant resources consumed managing large volumes
|
||||
// manually.
|
||||
// </span>
|
||||
// </li>
|
||||
// <li className="flex items-start">
|
||||
// <FaShieldAlt className="text-red-500 mt-1 mr-3 flex-shrink-0" />{" "}
|
||||
// {/* Using red for ineffective fraud detection */}
|
||||
// <span className="text-gray-700 dark:text-gray-300">
|
||||
// <strong className="dark:text-white">
|
||||
// Limited Fraud Detection:
|
||||
// </strong>{" "}
|
||||
// Manually identifying sophisticated fraud is difficult and
|
||||
// time-consuming.
|
||||
// </span>
|
||||
// </li>
|
||||
// <li className="flex items-start">
|
||||
// <FaBusinessTime
|
||||
// className="text-gold-500 mt-1 mr-3 flex-shrink-0"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <span className="text-gray-700 dark:text-gray-300">
|
||||
// <strong className="dark:text-white">
|
||||
// Long Turnaround Times:
|
||||
// </strong>{" "}
|
||||
// Slow processing leads to customer dissatisfaction and lost
|
||||
// opportunities.
|
||||
// </span>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </div>
|
||||
// <div className="relative h-72 md:h-96 rounded-lg overflow-hidden shadow-lg">
|
||||
// <Image
|
||||
// src="/images/manual-vs-automated.png" // Placeholder: Contrast image
|
||||
// alt="Manual data entry challenges vs automated efficiency"
|
||||
// layout="fill"
|
||||
// objectFit="cover"
|
||||
// className="transition-transform duration-500 hover:scale-105"
|
||||
// />
|
||||
// <div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent"></div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 3. Introducing OBSE: The Intelligent Solution --- */}
|
||||
// <section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
// <div className="relative h-72 md:h-96 rounded-lg overflow-hidden shadow-lg order-last md:order-first">
|
||||
// {/* Use the UI screenshot showing upload */}
|
||||
// <Image
|
||||
// src="/images/obse-ui-upload.png" // Placeholder: Catalogue Page 4 Left
|
||||
// alt="OBSE Bank Statement Upload Interface"
|
||||
// layout="fill"
|
||||
// objectFit="contain" // Use contain if you want to show the full UI without cropping
|
||||
// className="p-4 bg-gray-100 dark:bg-gray-800" // Added padding and background
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <FaLightbulb
|
||||
// className="text-5xl text-gold-500 mb-4"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
// OBSE: Automated Accuracy, Speed, and Insight
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4 font-poppins">
|
||||
// Owethu Bank Statement Extraction (OBSE) is an advanced OCR and
|
||||
// data aggregation platform designed specifically for the South
|
||||
// African financial landscape. It seamlessly extracts
|
||||
// comprehensive, accurate data from{" "}
|
||||
// <strong className="dark:text-white">
|
||||
// any SA bank statement
|
||||
// </strong>{" "}
|
||||
// (individual or juristic), regardless of format – including PDF
|
||||
// templates, scanned images, stamped documents, and even
|
||||
// disorganized statements.
|
||||
// </p>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-poppins font-semibold text-gold-600 dark:text-gold-400">
|
||||
// OBSE transforms raw bank statement data into actionable
|
||||
// financial intelligence in seconds, eliminating manual drudgery
|
||||
// and empowering your teams to make faster, smarter decisions.
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 4. How OBSE Works: Simple Steps, Powerful Results --- */}
|
||||
// <section
|
||||
// id="how-it-works"
|
||||
// className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700"
|
||||
// >
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-14">
|
||||
// <FaCogs
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// From PDF to Insights in Seconds
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// OBSE simplifies complex data extraction into a streamlined
|
||||
// workflow:
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Process Steps - Can use a grid or timeline structure */}
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-8 text-center">
|
||||
// {/* Step 1: Upload */}
|
||||
// <div className="flex flex-col items-center p-4">
|
||||
// <div className="bg-gold-500/20 p-4 rounded-full mb-4 border border-gold-500/50">
|
||||
// <FaUpload
|
||||
// className="text-3xl text-gold-500"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// </div>
|
||||
// <h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
// 1. Upload
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm">
|
||||
// Easily upload single or multiple bank statements (PDFs).
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Step 2: Extract */}
|
||||
// <div className="flex flex-col items-center p-4">
|
||||
// <div className="bg-gold-500/20 p-4 rounded-full mb-4 border border-gold-500/50">
|
||||
// <FaBolt
|
||||
// className="text-3xl text-gold-500"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// </div>
|
||||
// <h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
// 2. Automated Extraction
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm">
|
||||
// Powerful engine reads, structures, and validates data in seconds
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Step 3: Analyze & Check */}
|
||||
// <div className="flex flex-col items-center p-4">
|
||||
// <div className="bg-gold-500/20 p-4 rounded-full mb-4 border border-gold-500/50">
|
||||
// <FaShieldAlt
|
||||
// className="text-3xl text-gold-500"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// </div>
|
||||
// <h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
// 3. Analysis & Fraud Check
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm">
|
||||
// Analyzes transactions, identifies income, flags potential fraud.
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Step 4: Review */}
|
||||
// <div className="flex flex-col items-center p-4">
|
||||
// <div className="bg-gold-500/20 p-4 rounded-full mb-4 border border-gold-500/50">
|
||||
// <FaEye
|
||||
// className="text-3xl text-gold-500"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// </div>
|
||||
// <h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
// 4. Review & Interact (Optional)
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm">
|
||||
// User-friendly interface to review, recategorize (real-time), and
|
||||
// view insights.
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Step 5: Access/Integrate */}
|
||||
// <div className="flex flex-col items-center p-4">
|
||||
// <div className="bg-gold-500/20 p-4 rounded-full mb-4 border border-gold-500/50">
|
||||
// <FaPaperPlane
|
||||
// className="text-3xl text-gold-500"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// </div>
|
||||
// <h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
// 5. Access & Integrate
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm">
|
||||
// Export (Excel, PDF), use visual analytics, or integrate via API.
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// {/* Visual for the process flow */}
|
||||
// <div className="mt-12 relative h-60 md:h-96 rounded-lg overflow-hidden shadow-lg bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
|
||||
// <Image
|
||||
// src="/images/obse-process-flow.png" // Placeholder: Sequence of UI screenshots from Page 6
|
||||
// alt="OBSE Process Flow Visualization"
|
||||
// layout="fill"
|
||||
// objectFit="contain"
|
||||
// className="p-4"
|
||||
// />
|
||||
// <p className="absolute text-gray-500 dark:text-gray-400 z-0">
|
||||
// Process Flow Visual (Replace with Image)
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 5. Key Features: Powering Your Processes --- */}
|
||||
// <section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-14">
|
||||
// {/* <FaListCheck
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// /> */}
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// Unlock Deeper Financial Understanding
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// Explore the core capabilities that make OBSE an indispensable tool
|
||||
// for modern financial analysis.
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
// {keyFeatures.map((feature) => (
|
||||
// <div
|
||||
// key={feature.title}
|
||||
// className="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-md transform transition duration-300 hover:-translate-y-2 hover:shadow-xl dark:hover:shadow-gold-500/20 border-t-4 border-gold-500"
|
||||
// style={{ borderColor: "#e1c44a" }}
|
||||
// >
|
||||
// <feature.icon
|
||||
// className="text-4xl text-gold-500 mb-4" // Adjusted size
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-3 font-poppins dark:text-white">
|
||||
// {feature.title}
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
// {feature.description}
|
||||
// </p>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 6. Core Benefits: The OBSE Advantage (The Gain) --- */}
|
||||
// <section className="py-16 md:py-24 bg-gray-100 dark:bg-gray-800">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-14">
|
||||
// <FaThumbsUp
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// Transform Operations & Decision-Making
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// Discover the tangible benefits OBSE brings to your financial
|
||||
// workflows and bottom line.
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
// {coreBenefits.map((benefit) => (
|
||||
// <div
|
||||
// key={benefit.title}
|
||||
// className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-2 hover:shadow-xl dark:hover:shadow-gold-500/20"
|
||||
// >
|
||||
// <benefit.icon
|
||||
// className="text-4xl text-gold-500 mx-auto mb-4"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-2 font-poppins dark:text-white">
|
||||
// {benefit.title}
|
||||
// </h4>
|
||||
// <p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
// {benefit.description}
|
||||
// </p>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 7. Data Outputs & Insights --- */}
|
||||
// <section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
// <div>
|
||||
// <FaChartPie
|
||||
// className="text-5xl text-gold-500 mb-4"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
// Actionable Intelligence at Your Fingertips
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-6 font-poppins">
|
||||
// OBSE delivers a suite of standard financial summaries and
|
||||
// detailed data points essential for informed decisions. Gain
|
||||
// clarity on:
|
||||
// </p>
|
||||
// <div className="space-y-4">
|
||||
// {dataOutputs.map((output) => (
|
||||
// <div
|
||||
// key={output.title}
|
||||
// className="flex items-start p-3 bg-gray-50 dark:bg-gray-800 rounded-md border-l-4 border-gold-500/50"
|
||||
// style={{ borderColor: "rgba(225, 196, 74, 0.5)" }}
|
||||
// >
|
||||
// <output.icon
|
||||
// className="text-2xl text-gold-500 mt-1 mr-4 flex-shrink-0"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <div>
|
||||
// <h4 className="font-semibold font-poppins dark:text-white">
|
||||
// {output.title}
|
||||
// </h4>
|
||||
// <p className="text-sm text-gray-600 dark:text-gray-300 font-poppins">
|
||||
// {output.description}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="relative h-80 md:h-[500px] rounded-lg overflow-hidden shadow-lg bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
|
||||
// {/* Visual: Use dashboard examples from Page 6 */}
|
||||
// <Image
|
||||
// src="/images/obse-dashboard-visuals.png" // Placeholder: Catalogue Page 6 Dashboards
|
||||
// alt="OBSE Dashboard Examples showing financial insights"
|
||||
// layout="fill"
|
||||
// objectFit="contain" // Contain might be better for UI elements
|
||||
// className="p-4"
|
||||
// />
|
||||
// <p className="absolute text-gray-500 dark:text-gray-400 z-0">
|
||||
// Dashboard Visual (Replace with Image)
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 8. Technology Deep Dive --- */}
|
||||
// <section className="py-16 md:py-24 bg-gray-800 text-white">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-14">
|
||||
// <FaMicrochip
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-white mb-4">
|
||||
// Leveraging Advanced Technology
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// Built on a foundation of robust and intelligent technologies for
|
||||
// reliable, accurate results.
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
// {/* OCR Engine */}
|
||||
// <div className="bg-gray-700 p-6 rounded-lg text-center">
|
||||
// <FaBolt
|
||||
// className="text-4xl text-gold-500 mx-auto mb-3"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-2 font-poppins text-white">
|
||||
// Advanced OCR Engine
|
||||
// </h4>
|
||||
// <p className="text-gray-300 font-poppins text-sm">
|
||||
// Fine-tuned for SA bank statements, handles diverse formats,
|
||||
// scans, and obscured text.
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Machine Learning */}
|
||||
// <div className="bg-gray-700 p-6 rounded-lg text-center">
|
||||
// <FaBrain
|
||||
// className="text-4xl text-gold-500 mx-auto mb-3"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-2 font-poppins text-white">
|
||||
// Machine Learning
|
||||
// </h4>
|
||||
// <p className="text-gray-300 font-poppins text-sm">
|
||||
// Continuously improves interpretation, reconciles discrepancies,
|
||||
// and powers fraud detection models.
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Data Structuring */}
|
||||
// <div className="bg-gray-700 p-6 rounded-lg text-center">
|
||||
// <FaTable
|
||||
// className="text-4xl text-gold-500 mx-auto mb-3"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-2 font-poppins text-white">
|
||||
// Data Structuring & Validation
|
||||
// </h4>
|
||||
// <p className="text-gray-300 font-poppins text-sm">
|
||||
// Intelligently structures extracted data (e.g., JSON), validates
|
||||
// entries, applies rules.
|
||||
// </p>
|
||||
// </div>
|
||||
// {/* Secure API */}
|
||||
// <div className="bg-gray-700 p-6 rounded-lg text-center">
|
||||
// <FaNetworkWired
|
||||
// className="text-4xl text-gold-500 mx-auto mb-3"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h4 className="text-xl font-semibold mb-2 font-poppins text-white">
|
||||
// Secure API
|
||||
// </h4>
|
||||
// <p className="text-gray-300 font-poppins text-sm">
|
||||
// Robust and secure API for seamless, safe integration with your
|
||||
// existing systems.
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// {/* Visual: JSON code snippet visual from Page 5 */}
|
||||
// <div className="mt-12 relative h-48 md:h-64 rounded-lg overflow-hidden shadow-lg bg-black flex items-center justify-center">
|
||||
// <Image
|
||||
// src="/images/obse-json-output.png" // Placeholder: Catalogue Page 5 JSON Visual
|
||||
// alt="Example JSON output from OBSE data extraction"
|
||||
// layout="fill"
|
||||
// objectFit="contain"
|
||||
// className="p-4 opacity-80"
|
||||
// />
|
||||
// <p className="absolute text-gray-500 z-0">
|
||||
// JSON Output Example (Replace with Image)
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 9. Use Cases & Applications --- */}
|
||||
// <section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-14">
|
||||
// <FaBriefcase
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// Empowering Various Financial Processes
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// OBSE delivers value across a wide range of applications within the
|
||||
// financial services sector.
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
// {useCases.map((useCase) => (
|
||||
// <div
|
||||
// key={useCase.title}
|
||||
// className="bg-white dark:bg-gray-700 p-5 rounded-lg shadow-sm border-l-4 border-gold-500 flex items-center"
|
||||
// style={{ borderColor: "#e1c44a" }}
|
||||
// >
|
||||
// <FaCheckCircle
|
||||
// className="text-gold-500 mr-3 flex-shrink-0"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <span className="font-poppins dark:text-white text-sm font-medium">
|
||||
// {useCase.title}
|
||||
// </span>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 10. Customization & Partnership --- */}
|
||||
// <section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
// <div className="relative h-64 md:h-80 rounded-lg overflow-hidden shadow-lg order-last md:order-first">
|
||||
// <Image
|
||||
// src="/images/partnership-handshake.jpg" // Placeholder: Image representing partnership
|
||||
// alt="OMS and Client Partnership"
|
||||
// layout="fill"
|
||||
// objectFit="cover"
|
||||
// className="transition-transform duration-500 hover:scale-105"
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <FaUsersCog
|
||||
// className="text-5xl text-gold-500 mb-4"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
// Flexible Solutions, Tailored To You
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4 font-poppins">
|
||||
// While OBSE offers a comprehensive standard package, we
|
||||
// understand that each organization has unique needs. If you
|
||||
// require specific information or analysis not included,{" "}
|
||||
// <strong className="dark:text-white">
|
||||
// we are committed to collaborating with you to develop
|
||||
// tailor-made outputs
|
||||
// </strong>{" "}
|
||||
// that align perfectly with your requirements.
|
||||
// </p>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-6 font-poppins">
|
||||
// Our goal is to ensure OBSE meets and exceeds your expectations,
|
||||
// providing the exact insights needed for your operational
|
||||
// processes and decision-making.
|
||||
// </p>
|
||||
// {/* POC Callout */}
|
||||
// <div
|
||||
// className="bg-gold-50 dark:bg-gold-900/20 border-l-4 border-gold-500 p-4 rounded-md"
|
||||
// style={{ borderColor: "#e1c44a" }}
|
||||
// >
|
||||
// <h4 className="font-semibold font-poppins text-gold-800 dark:text-gold-300">
|
||||
// Proof of Concept (POC) Available
|
||||
// </h4>
|
||||
// <p className="text-sm text-gold-700 dark:text-gold-400 font-poppins mt-1">
|
||||
// We offer Proof of Concept engagements to demonstrate OBSE's
|
||||
// capabilities with your specific data and workflows before full
|
||||
// commitment. Let's discuss your needs.
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 11. About OMS: Your Trusted Technology Partner --- */}
|
||||
// <section className="py-16 md:py-24 bg-gray-100 dark:bg-gray-800">
|
||||
// <div className="container mx-auto px-6 text-center">
|
||||
// {/* Optional: Simple OMS logo here */}
|
||||
// <Image
|
||||
// src="/images/oms-logo-dark.png" // Placeholder: Dark version of logo
|
||||
// alt="Owethu Management Solutions Logo"
|
||||
// width={150}
|
||||
// height={75}
|
||||
// className="h-12 w-auto mx-auto mb-6"
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// Your Experienced Technology Partner
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins mb-6">
|
||||
// Owethu Management Solutions (OMS) provides professional technology
|
||||
// services with a proven track record, including participation in
|
||||
// ABSA's supplier development program since 2020. OBSE is backed by
|
||||
// our dedicated team of IT Managers, Senior Developers, and UX
|
||||
// Designers committed to robust, user-friendly solutions.
|
||||
// </p>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins font-semibold">
|
||||
// We focus on leveraging technology to solve real-world business
|
||||
// challenges in the financial sector, emphasizing efficiency,
|
||||
// accuracy, and strong partnerships.
|
||||
// </p>
|
||||
// <div className="mt-8">
|
||||
// <Link
|
||||
// href="/about-us"
|
||||
// className="inline-block text-gold-600 dark:text-gold-400 font-semibold py-2 px-4 rounded-md hover:text-gold-700 dark:hover:text-gold-300 hover:bg-gold-500/10 transition-colors duration-300 font-poppins"
|
||||
// style={{ color: "#c8a93d" }} // Using a slightly darker gold for text link
|
||||
// >
|
||||
// Learn More About OMS <FaArrowRight className="inline ml-1" />
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 12. Call to Action (CTA) --- */}
|
||||
// <section
|
||||
// className="py-16 md:py-24 bg-gradient-to-r from-gold-500 to-gold-600 text-gray-900 dark:from-gold-600 dark:to-gold-700"
|
||||
// style={{ backgroundColor: "#e1c44a" }}
|
||||
// >
|
||||
// <div className="container mx-auto px-6 text-center">
|
||||
// <FaBolt className="text-5xl mx-auto mb-5 text-gray-800" />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-gray-900">
|
||||
// Ready to Transform Your Bank Statement Processing?
|
||||
// </h2>
|
||||
// <p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed font-poppins text-gray-800 mb-8">
|
||||
// See OBSE in action! Schedule a live demo tailored to your specific
|
||||
// use case and discover how OBSE can streamline your operations,
|
||||
// reduce costs, and mitigate risk.
|
||||
// </p>
|
||||
// <div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
// <Link
|
||||
// href="#contact" // Link to contact form/section below
|
||||
// className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300 font-poppins"
|
||||
// >
|
||||
// Request a Personalized Demo
|
||||
// </Link>
|
||||
// <Link
|
||||
// href="/contact" // Link to general contact page
|
||||
// className="inline-block bg-transparent border-2 border-gray-800 text-gray-800 font-bold py-3 px-8 rounded-md hover:bg-gray-800/10 transition-colors duration-300 font-poppins"
|
||||
// >
|
||||
// Contact Sales Team
|
||||
// </Link>
|
||||
// </div>
|
||||
// {/* Optional Tertiary Link */}
|
||||
// {/* <div className="mt-4">
|
||||
// <a href="/path/to/obse-brochure.pdf" target="_blank" rel="noopener noreferrer" className="text-sm font-medium text-gray-700 hover:text-black font-poppins">
|
||||
// Download Brochure
|
||||
// </a>
|
||||
// </div> */}
|
||||
// </div>
|
||||
// </section>
|
||||
|
||||
// {/* --- 13. Contact Information --- */}
|
||||
// <section
|
||||
// id="contact"
|
||||
// className="py-16 md:py-24 bg-white dark:bg-gray-900"
|
||||
// >
|
||||
// <div className="container mx-auto px-6">
|
||||
// <div className="text-center mb-12">
|
||||
// <FaPhoneAlt
|
||||
// className="text-5xl text-gold-500 mx-auto mb-5"
|
||||
// style={{ color: "#e1c44a" }}
|
||||
// />
|
||||
// <h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
// Get in Touch
|
||||
// </h2>
|
||||
// <p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
// For personalized support or to schedule your demo, reach out to
|
||||
// us. Let's harness the power of automated data extraction for your
|
||||
// business.
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="max-w-xl mx-auto text-center">
|
||||
// <div className="mb-6">
|
||||
// <h4 className="text-lg font-semibold font-poppins dark:text-white">
|
||||
// Contact Details:
|
||||
// </h4>
|
||||
// <p className="text-gray-700 dark:text-gray-300 font-poppins mt-2">
|
||||
// <strong className="dark:text-white">Phone:</strong>{" "}
|
||||
// <a
|
||||
// href="tel:+27120513281"
|
||||
// className="text-gold-600 hover:text-gold-700 dark:text-gold-400 dark:hover:text-gold-300"
|
||||
// >
|
||||
// (012) 051 3281
|
||||
// </a>
|
||||
// </p>
|
||||
// <p className="text-gray-700 dark:text-gray-300 font-poppins mt-1">
|
||||
// <strong className="dark:text-white">Email:</strong>{" "}
|
||||
// <a
|
||||
// href="mailto:Zanelem@oms.africa"
|
||||
// className="text-gold-600 hover:text-gold-700 dark:text-gold-400 dark:hover:text-gold-300"
|
||||
// >
|
||||
// Zanelem@oms.africa
|
||||
// </a>
|
||||
// </p>
|
||||
// </div>
|
||||
|
||||
// {/* Optional: Placeholder for Contact Form */}
|
||||
// {/* <div className="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
|
||||
// <h4 className="text-lg font-semibold font-poppins dark:text-white mb-4">Or Send Us a Message</h4>
|
||||
// <p className="text-gray-600 dark:text-gray-400 font-poppins">Contact form component would go here.</p>
|
||||
// <Link href="/contact" className="mt-4 inline-block bg-gold-500 text-gray-900 font-bold py-2 px-6 rounded-md hover:bg-gold-600 transition-colors duration-300 font-poppins" style={{backgroundColor: "#e1c44a"}}>Go to Contact Form</Link>
|
||||
// </div> */}
|
||||
// </div>
|
||||
// </div>
|
||||
// </section>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
859
app/(website)/obse/page.tsx
Normal file
@ -0,0 +1,859 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next"; // Added import
|
||||
import {
|
||||
FaCheckCircle,
|
||||
FaCogs,
|
||||
FaDatabase,
|
||||
FaExclamationTriangle,
|
||||
FaFileAlt,
|
||||
FaFileInvoiceDollar,
|
||||
FaFingerprint,
|
||||
FaHandshake,
|
||||
FaHistory,
|
||||
FaLaptopCode,
|
||||
FaLayerGroup,
|
||||
FaLightbulb,
|
||||
FaPlayCircle,
|
||||
FaPuzzlePiece,
|
||||
FaSearchDollar,
|
||||
FaShieldAlt,
|
||||
FaShippingFast,
|
||||
FaSignInAlt,
|
||||
FaSpinner,
|
||||
FaSyncAlt,
|
||||
FaTachometerAlt,
|
||||
FaUserCheck,
|
||||
FaUsersCog,
|
||||
FaWrench,
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants"; // Assuming COLORS constant is available
|
||||
// import ContactForm from "@/components/ContactForm"; // Assuming ContactForm is available
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Automated Bank Statement Extraction South Africa | OBSE by OMS",
|
||||
description:
|
||||
"Revolutionize lending with OBSE: Automated bank statement data extraction, OCR, fraud detection, and analysis for South African financial institutions. Improve accuracy, speed, and decision-making.",
|
||||
keywords: [
|
||||
"Optimized Bank Statement Extractor",
|
||||
"OBSE",
|
||||
"bank statement extraction",
|
||||
"OCR bank statements",
|
||||
"automated financial data",
|
||||
"South Africa",
|
||||
"lending automation",
|
||||
"credit risk assessment",
|
||||
"income verification",
|
||||
"affordability calculation",
|
||||
"fraud detection",
|
||||
"financial technology",
|
||||
"fintech SA",
|
||||
"OMS",
|
||||
"Owethu Management Services",
|
||||
],
|
||||
// Optional: Add Open Graph and Twitter Card metadata for social sharing
|
||||
openGraph: {
|
||||
title: "Automated Bank Statement Extraction South Africa | OBSE by OMS",
|
||||
description:
|
||||
"Streamline your financial processes with OBSE's intelligent bank statement analysis.",
|
||||
url: "https://www.oms.africa/obse", // Replace with your actual URL
|
||||
images: [
|
||||
{
|
||||
url: "/images/obse-og-image.png", // Replace with an appropriate OG image URL
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "OBSE Automated Bank Statement Extraction",
|
||||
},
|
||||
],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Automated Bank Statement Extraction South Africa | OBSE by OMS",
|
||||
description:
|
||||
"Fast, accurate bank statement processing for SA lenders with OBSE.",
|
||||
// images: ['/images/obse-twitter-image.png'], // Replace with Twitter-specific image if needed
|
||||
},
|
||||
};
|
||||
|
||||
// Define structure for features, benefits, etc. if needed for mapping
|
||||
interface FeatureItem {
|
||||
icon: React.ElementType;
|
||||
title: string;
|
||||
description: string;
|
||||
isComingSoon?: boolean;
|
||||
}
|
||||
|
||||
const keyFeatures: FeatureItem[] = [
|
||||
{
|
||||
icon: FaShippingFast,
|
||||
title: "Automated Data Extraction",
|
||||
description:
|
||||
"Processes statements in seconds (avg. 9-12s), drastically reducing manual effort.",
|
||||
},
|
||||
{
|
||||
icon: FaFileAlt,
|
||||
title: "Advanced OCR Technology",
|
||||
description:
|
||||
"High accuracy, handles various fonts, obscured text (stamps), and similar characters.",
|
||||
},
|
||||
{
|
||||
icon: FaDatabase,
|
||||
title: "Comprehensive SA Bank Coverage",
|
||||
description:
|
||||
"Handles statements from all major SA banks and various formats (PDF, scanned, stamped).",
|
||||
},
|
||||
{
|
||||
icon: FaSearchDollar,
|
||||
title: "Intelligent Income & Salaried Detection",
|
||||
description:
|
||||
"Accurately detects both salaried and non-salaried income, explains findings, and intelligently filters out internal transfers for clearer financial insights. ",
|
||||
},
|
||||
{
|
||||
icon: FaFingerprint,
|
||||
title: "Enhanced Fraud Detection",
|
||||
description:
|
||||
"Detects document tampering, fraudulent insertions, and developing behavioral profiling.",
|
||||
isComingSoon: false,
|
||||
},
|
||||
{
|
||||
icon: FaSyncAlt,
|
||||
title: "Dynamic Data Interaction",
|
||||
description:
|
||||
"User-friendly interface for real-time transaction recategorization with immediate recalculations.",
|
||||
},
|
||||
{
|
||||
icon: FaLaptopCode,
|
||||
title: "Seamless API Integration",
|
||||
description:
|
||||
"Push structured, validated data directly into your credit scoring or loan origination systems.",
|
||||
},
|
||||
{
|
||||
icon: FaTachometerAlt,
|
||||
title: "Visual Analytics & Reporting",
|
||||
description:
|
||||
"Intuitive dashboards for insights into cash flow, income sources, and spending patterns.",
|
||||
},
|
||||
{
|
||||
icon: FaUsersCog,
|
||||
title: "MI Dashboard & Performance Tracking",
|
||||
description:
|
||||
"Monitor processing volumes, success rates, average times, and user performance.",
|
||||
},
|
||||
{
|
||||
icon: FaSignInAlt,
|
||||
title: "Multiple Export Formats",
|
||||
description:
|
||||
"Download results as structured Excel, formatted PDFs, or copy data easily.",
|
||||
},
|
||||
{
|
||||
icon: FaCheckCircle,
|
||||
title: "Built-in Accuracy Checks",
|
||||
description:
|
||||
"Includes automated validations and data reviews for uncertain extractions.",
|
||||
},
|
||||
{
|
||||
icon: FaHistory,
|
||||
title: "Comprehensive Audit Trail",
|
||||
description:
|
||||
"Maintains a detailed log of processing steps and user interactions for compliance and traceability.",
|
||||
},
|
||||
];
|
||||
|
||||
const coreBenefits: FeatureItem[] = [
|
||||
{
|
||||
icon: FaShippingFast,
|
||||
title: "Dramatically Reduce Processing Time",
|
||||
description: "Cut statement analysis from hours to minutes/seconds.",
|
||||
},
|
||||
{
|
||||
icon: FaCheckCircle,
|
||||
title: "Ensure High Accuracy & Reliability",
|
||||
description: "Minimize human error with advanced OCR and validation.",
|
||||
},
|
||||
{
|
||||
icon: FaFileInvoiceDollar,
|
||||
title: "Lower Operational Costs",
|
||||
description: "Reduce reliance on manual data entry staff and overheads.",
|
||||
},
|
||||
{
|
||||
icon: FaShieldAlt,
|
||||
title: "Strengthen Fraud Prevention",
|
||||
description:
|
||||
"Proactively identify tampered documents and suspicious activity.",
|
||||
},
|
||||
{
|
||||
icon: FaTachometerAlt,
|
||||
title: "Accelerate Turnaround Times",
|
||||
description:
|
||||
"Speed up loan applications, credit assessments, and onboarding.",
|
||||
},
|
||||
{
|
||||
icon: FaLightbulb,
|
||||
title: "Improve Decision-Making",
|
||||
description: "Base assessments on accurate, comprehensive, fast data.",
|
||||
},
|
||||
{
|
||||
icon: FaCogs,
|
||||
title: "Enhance Operational Efficiency",
|
||||
description:
|
||||
"Streamline workflows and free up staff for higher-value tasks.",
|
||||
},
|
||||
{
|
||||
icon: FaUserCheck,
|
||||
title: "Improve Compliance",
|
||||
description: "Ensure consistent and accurate data handling.",
|
||||
},
|
||||
];
|
||||
|
||||
const dataOutputs = [
|
||||
"Basic Transaction Summary",
|
||||
"Debit Order Analysis",
|
||||
"Disposable Income Calculation",
|
||||
"Affordability Assessment Data",
|
||||
"Income & Expenses Breakdown",
|
||||
"Detailed Transaction List (Exportable)",
|
||||
"Fraud Indicators & Flags",
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
"Lending & Credit Origination (Personal, Vehicle, SME)",
|
||||
"Credit Risk Assessment",
|
||||
"Income Verification Automation",
|
||||
"Affordability Calculations",
|
||||
"Customer Onboarding (KYC)",
|
||||
"Financial Health Monitoring",
|
||||
"Internal Audit & Reconciliation",
|
||||
"Fraud Investigation Support",
|
||||
];
|
||||
|
||||
export default function ObsePage() {
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200 font-poppins">
|
||||
{/* 1. Hero Section */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-900 text-white py-20 md:py-32">
|
||||
<div className="absolute inset-0 bg-black opacity-40 dark:opacity-60"></div>
|
||||
{/* Optional: Add a subtle background pattern or image */}
|
||||
{/* <div className="absolute inset-0 bg-[url('/path/to/hero-bg.png')] bg-cover bg-center opacity-10"></div> */}
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
{/* Consider adding OMS Logo here */}
|
||||
{/* <Image src="/oms-logo-white.png" alt="OMS Logo" width={150} height={50} className="mx-auto mb-6" /> */}
|
||||
<h1
|
||||
className="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md leading-tight"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Revolutionize Your Lending and <br />
|
||||
Credit Processes with OBSE
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-4xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 mb-8">
|
||||
Automate data extraction, enhance accuracy, detect fraud, and
|
||||
accelerate decision-making with South Africa's intelligent bank
|
||||
statement solution.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="https://youtu.be/Sd3TnvoLtDA?si=UOeXScbosM5LZxbg" // Link to the "How it Works" section
|
||||
className="inline-flex items-center justify-center bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 transition-colors duration-300"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<FaPlayCircle className="inline mr-2 text-red-400" /> See How It
|
||||
Works
|
||||
</Link>
|
||||
|
||||
{/* Optional Mini-CTA */}
|
||||
{/* <Link href="#overview-video" className="inline-flex items-center text-gold-400 hover:text-gold-300 transition-colors duration-300">
|
||||
<FaPlayCircle className="mr-2" /> Watch a Quick Overview
|
||||
</Link> */}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. The Challenge Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
The High Cost of Manual Bank Statement Processing
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
In the rapidly evolving landscape of financial services, where
|
||||
precision and speed are paramount, the challenges associated with
|
||||
extracting data from bank statements can become significant
|
||||
roadblocks. Efficiently parsing financial information is critical
|
||||
for accurately assessing financial health and risk.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
icon: FaSpinner,
|
||||
title: "Slow & Labor-Intensive",
|
||||
desc: "Teams spend countless hours manually keying in data, causing operational bottlenecks.",
|
||||
},
|
||||
{
|
||||
icon: FaExclamationTriangle,
|
||||
title: "Prone to Human Error",
|
||||
desc: "Manual entry risks inaccurate assessments, compliance breaches, and poor decisions.",
|
||||
},
|
||||
{
|
||||
icon: FaFileInvoiceDollar,
|
||||
title: "High Operational Costs",
|
||||
desc: "Significant resources consumed managing large volumes of statements manually.",
|
||||
},
|
||||
{
|
||||
icon: FaShieldAlt,
|
||||
title: "Limited Fraud Detection",
|
||||
desc: "Identifying sophisticated fraud or tampered documents manually is difficult and slow.",
|
||||
},
|
||||
{
|
||||
icon: FaTachometerAlt,
|
||||
title: "Long Turnaround Times",
|
||||
desc: "Slow processing delays approvals, causing customer dissatisfaction and missed opportunities.",
|
||||
},
|
||||
{
|
||||
icon: FaLayerGroup,
|
||||
title: "Scalability Challenges",
|
||||
desc: "Manual workflows bottleneck quickly, making it difficult to handle growing statement volumes efficiently.",
|
||||
},
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl 0 mx-auto mb-4"
|
||||
style={{ color: "#e1c44a" }} // Using red to signify pain points
|
||||
//text-red-500 dark:text-red-40
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||
{item.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Introducing OBSE Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
OBSE: Automated Accuracy, Speed, and Insight
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4">
|
||||
Optimized Bank Statement Extractor (OBSE) is an advanced OCR and
|
||||
data aggregation platform designed specifically for the South
|
||||
African financial landscape. It seamlessly extracts
|
||||
comprehensive, accurate data from any bank statement (individual
|
||||
or juristic, across all SA banks), regardless of format –
|
||||
including PDFs, scanned images, stamped documents, and even
|
||||
disorganized statements.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-semibold">
|
||||
OBSE transforms raw bank statement data into actionable
|
||||
financial intelligence in seconds, eliminating manual drudgery
|
||||
and empowering your teams to make faster, smarter decisions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative h-64 md:h-80 rounded-lg overflow-hidden shadow-lg">
|
||||
{/* Placeholder for UI Screenshot */}
|
||||
<Image
|
||||
src="/images/obse.svg" // Replace with actual path
|
||||
alt="OBSE Upload Interface"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="bg-gray-200 dark:bg-gray-700" // Placeholder background
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. How OBSE Works Section */}
|
||||
<section
|
||||
id="how-it-works"
|
||||
className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
From PDF to Insights in Seconds
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
OBSE simplifies complex data extraction into a streamlined
|
||||
workflow.
|
||||
</p>
|
||||
</div>
|
||||
{/* Consider a visual flow diagram or stepped cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6 text-center">
|
||||
{[
|
||||
{
|
||||
step: 1,
|
||||
title: "Upload",
|
||||
desc: "Easily upload single multi-month PDFs or separate monthly files.",
|
||||
icon: FaSignInAlt,
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Automated Extraction",
|
||||
desc: "Powerful engine reads, structures, and validates data in seconds.",
|
||||
icon: FaCogs,
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Analysis & Fraud Checks",
|
||||
desc: "Analyzes transactions, identifies income, flags fraud indicators.",
|
||||
icon: FaSearchDollar,
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: "Review & Interact",
|
||||
desc: "User-friendly interface to review, recategorize, and view insights.",
|
||||
icon: FaUserCheck,
|
||||
},
|
||||
{
|
||||
step: 5,
|
||||
title: "Access & Integrate",
|
||||
desc: "Export data (Excel, PDF) or integrate results via API.",
|
||||
icon: FaLaptopCode,
|
||||
},
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.step}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md"
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold text-xl"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
{item.step}
|
||||
</div>
|
||||
<item.icon
|
||||
className="text-3xl text-gold-500 dark:text-gold-400 mx-auto mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||
{item.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Key Features Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Unlock Deeper Financial Understanding
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Explore the powerful capabilities that drive OBSE's
|
||||
performance.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{keyFeatures.map((feature) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className="relative bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm border-t-4 border-gold-500 dark:border-gold-400 transition-shadow hover:shadow-md"
|
||||
style={{ borderColor: COLORS.primary }}
|
||||
>
|
||||
{/*feature.isComingSoon && (
|
||||
<span className="absolute top-2 right-2 px-3 py-1 text-sm font-bold bg-cyan-700 text-white rounded-full shadow-lg z-10 font-poppins">
|
||||
Coming Soon
|
||||
</span>
|
||||
)*/}
|
||||
<feature.icon
|
||||
className="text-3xl mb-3 text-gold-500 dark:text-gold-400"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{feature.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 6. Core Benefits Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
The OBSE Advantage: Transform Your Operations
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Experience tangible benefits that impact your bottom line and
|
||||
efficiency.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{coreBenefits.map((benefit) => (
|
||||
<div
|
||||
key={benefit.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<benefit.icon
|
||||
className="text-4xl text-gold-500 dark:text-gold-400 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{benefit.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{benefit.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 7. Data Outputs & Insights Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div className="relative h-64 md:h-96 rounded-lg overflow-hidden shadow-lg">
|
||||
{/* Placeholder for Dashboard Example */}
|
||||
<Image
|
||||
src="/images/dashboard.png" // Replace with actual path
|
||||
alt="OBSE Dashboard Insights"
|
||||
layout="fill"
|
||||
objectFit="contain" // Use contain if showing a full dashboard
|
||||
className="bg-gray-100 dark:bg-gray-800 p-4" // Add padding if needed
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
Actionable Intelligence at Your Fingertips
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-6">
|
||||
OBSE delivers a suite of standard financial summaries and
|
||||
detailed data points essential for informed decisions:
|
||||
</p>
|
||||
<ul className="space-y-3">
|
||||
{dataOutputs.map((output) => (
|
||||
<li key={output} className="flex items-start">
|
||||
<FaCheckCircle
|
||||
className="w-5 h-5 text-green-500 dark:text-green-400 mr-3 mt-1 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Or use primary color
|
||||
/>
|
||||
<span className="text-gray-700 dark:text-gray-300">
|
||||
{output}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 8. Technology Deep Dive Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-800 text-white">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<FaCogs
|
||||
className="text-5xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins mb-4">
|
||||
Leveraging Advanced Technology for Reliable Results
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-300">
|
||||
Built on a foundation of cutting-edge technology to ensure
|
||||
performance and accuracy.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-6xl mx-auto">
|
||||
{[
|
||||
{
|
||||
title: "Advanced OCR Engine",
|
||||
desc: "Fine-tuned for diverse SA bank statements, handling stamps and challenging inputs.",
|
||||
icon: FaFileAlt,
|
||||
},
|
||||
{
|
||||
title: "Machine Learning",
|
||||
desc: "Continuously improves interpretation, context analysis, and fraud detection models.",
|
||||
icon: FaLightbulb,
|
||||
},
|
||||
{
|
||||
title: "Data Structuring & Validation",
|
||||
desc: "Intelligently structures data, validates entries, and applies rules for consistency.",
|
||||
icon: FaPuzzlePiece,
|
||||
},
|
||||
{
|
||||
title: "Secure API",
|
||||
desc: "Robust and secure API for seamless and safe integration with your existing systems.",
|
||||
icon: FaShieldAlt,
|
||||
},
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-gray-700 p-6 rounded-lg text-center"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-300 text-sm">{item.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Optional: Add JSON snippet visual */}
|
||||
{/* <div className="mt-12 max-w-2xl mx-auto bg-gray-900 p-4 rounded-lg shadow-lg">
|
||||
<pre><code className="language-json text-sm">
|
||||
{`{
|
||||
"transaction_id": "T12345",
|
||||
"date": "2023-10-26",
|
||||
"description": "SALARY ABC CORP",
|
||||
"amount": 15000.00,
|
||||
"type": "credit",
|
||||
"category": "Income/Salary"
|
||||
}`}
|
||||
</code></pre>
|
||||
</div> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 9. Use Cases & Applications Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<FaWrench
|
||||
className="text-5xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Empowering Various Financial Processes
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
OBSE provides critical data and automation for a wide range of
|
||||
applications.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-6 max-w-5xl mx-auto">
|
||||
{useCases.map((useCase) => (
|
||||
<div
|
||||
key={useCase}
|
||||
className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg shadow-sm text-center"
|
||||
>
|
||||
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{useCase}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 10. Customization & Partnership Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaHandshake
|
||||
className="text-5xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Flexible Solutions Tailored to Your Needs
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-4xl mx-auto mb-6 leading-relaxed">
|
||||
Beyond our standard offerings, we understand unique needs. If you
|
||||
require specific information or analysis not in our standard
|
||||
package, we'll collaborate to develop tailor-made outputs
|
||||
aligned with your requirements. Our goal is to ensure OBSE exceeds
|
||||
your expectations.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-800 dark:text-gray-200 font-semibold max-w-4xl mx-auto">
|
||||
We offer Proof of Concept (POC) engagements to demonstrate
|
||||
OBSE's capabilities with your data and workflows, allowing you
|
||||
to evaluate its potential impact before full commitment.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 11. About OMS Section (Brief)
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
{ Optional: OMS Logo }
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Your Experienced Partner in Financial Technology
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-6">
|
||||
Owethu Management Solutions (OMS) provides professional technology
|
||||
services with a proven track record, including participation in
|
||||
ABSA's supplier development program since 2020. OBSE is backed
|
||||
by a dedicated team committed to delivering robust, user-friendly
|
||||
solutions that solve real-world business challenges.
|
||||
</p>
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300 inline-flex items-center"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Learn More About OMS <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
/}
|
||||
|
||||
{/* 12. Call to Action (CTA) Section */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
Ready to Transform Your Bank Statement Processing?
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed mb-8 font-poppins text-gray-800">
|
||||
See OBSE in action! Schedule a live demo tailored to your specific
|
||||
use case and discover how OBSE can streamline your operations,
|
||||
reduce costs, and mitigate risk.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="/contact" // Pre-fill subject for contact form
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300"
|
||||
>
|
||||
Request a Personalized Demo
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact" // Pre-fill subject
|
||||
className="inline-block bg-transparent border-2 border-gray-800 text-gray-800 font-bold py-3 px-8 rounded-md hover:bg-gray-800 hover:text-white transition-colors duration-300"
|
||||
>
|
||||
Contact Sales Team
|
||||
</Link>
|
||||
</div>
|
||||
{/* Optional: Download Brochure Link */}
|
||||
{/* <div className="mt-6">
|
||||
<a href="/path/to/obse-brochure.pdf" download className="text-sm text-gray-800 hover:underline">
|
||||
Download Brochure
|
||||
</a>
|
||||
</div> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 13. Contact Information Section */}
|
||||
{/*}
|
||||
<section className="py-16 md:py-24 bg-gray-100 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
For personalized support or to schedule a demo, reach out to us.
|
||||
Our team is here to help you harness the power of automated data
|
||||
extraction. Take the first step toward enhancing your efficiency
|
||||
and driving your success!
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
||||
{ -----Contact Details */}
|
||||
|
||||
{/* Optional: Add a contact form component here if available ---
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<FaPhone
|
||||
className="w-6 h-6 text-gold-500 dark:text-gold-400 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Phone
|
||||
</h3>
|
||||
<a
|
||||
href="tel:+27120513281" // Updated phone number
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm"
|
||||
>
|
||||
(012) 051 3281
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<FaEnvelope
|
||||
className="w-6 h-6 text-gold-500 dark:text-gold-400 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Email
|
||||
</h3>
|
||||
<a
|
||||
href="mailto:hello@oms.africa" // Updated email //
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm"
|
||||
>
|
||||
hello@oms.africa
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{--- Optional: Link to main contact page */}
|
||||
{/* --- comment out if not needed}
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex items-center text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Go to Full Contact Page <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
{/* Optional: Simple Contact Form (if ContactForm component is not used/available) */}
|
||||
{/* <form className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
|
||||
<input type="text" id="name" name="name" required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-gold-500 focus:border-gold-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Email</label>
|
||||
<input type="email" id="email" name="email" required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-gold-500 focus:border-gold-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Message</label>
|
||||
<textarea id="message" name="message" rows={4} required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-gold-500 focus:border-gold-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"></textarea>
|
||||
</div>
|
||||
<button type="submit" className="w-full bg-gold-500 text-gray-900 font-bold py-2 px-4 rounded-md hover:bg-gold-600 transition-colors duration-300" style={{ backgroundColor: COLORS.primary }}>
|
||||
Send Message
|
||||
</button>
|
||||
</form> */}
|
||||
|
||||
{/* OR Use the ContactForm Component
|
||||
<div className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md">
|
||||
<h3 className="text-xl font-semibold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Send a Quick Inquiry
|
||||
</h3>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
|
||||
{/* 14. Footer Section */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
286
app/(website)/offerings/page.tsx
Normal file
@ -0,0 +1,286 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
FaArrowRight,
|
||||
FaBriefcase,
|
||||
FaCogs,
|
||||
FaHandshake,
|
||||
FaLaptopCode,
|
||||
FaProjectDiagram,
|
||||
FaUsers,
|
||||
FaWrench,
|
||||
FaShippingFast,
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants"; // Assuming COLORS constant is available
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"Explore IT services by Owethu Managed Services (OMS): Custom Software Development, IT Resource Augmentation, and the OBSE Bank Statement Extractor. Partner with us for innovative solutions.",
|
||||
keywords: [
|
||||
"IT services South Africa",
|
||||
"custom software development",
|
||||
"IT resource augmentation",
|
||||
"managed IT services",
|
||||
"OBSE",
|
||||
"bank statement automation",
|
||||
"fintech solutions",
|
||||
"IT consulting",
|
||||
"OMS",
|
||||
"CVEvolve",
|
||||
"Owethu Managed Services",
|
||||
"Centurion",
|
||||
"Gauteng",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Comprehensive IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"OMS offers tailored IT solutions including custom development, resource augmentation, and specialized products like OBSE.",
|
||||
url: "https://oms.africa/services", // Update with the final URL
|
||||
images: [
|
||||
{
|
||||
url: "/images/oms-services-og.png", // Replace with an appropriate OG image URL
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "OMS IT Services",
|
||||
},
|
||||
],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Comprehensive IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"Partner with OMS for expert IT services, from custom builds to resource scaling.",
|
||||
// images: ['/images/oms-services-twitter.png'], // Replace if needed
|
||||
},
|
||||
};
|
||||
|
||||
// Data for Service Areas
|
||||
const serviceAreas = [
|
||||
{
|
||||
title: "Custom Software Development & Consulting",
|
||||
icon: FaLaptopCode,
|
||||
description:
|
||||
"We design, build, and implement bespoke software solutions tailored to your unique business challenges and objectives. Our consulting services help align technology with your strategic goals.",
|
||||
imageUrl: "/custome-software-3.png", // Reusing image from Custom Software Development
|
||||
//imageUrl: "/images/team.svg", // Reusing image from About
|
||||
link: "/contact?subject=Custom Development Inquiry", // Link to contact or a future dedicated page
|
||||
linkText: "Discuss Your Project",
|
||||
},
|
||||
{
|
||||
title: "IT Resource Augmentation",
|
||||
icon: FaUsers,
|
||||
description:
|
||||
"Scale your team effectively with our flexible resource augmentation model. Access skilled IT professionals (BAs, Testers, Developers, Designers) on demand or via managed milestone-based projects.",
|
||||
imageUrl: "/images/team-collaborative-2.png", // Reusing image from Resource Augmentation
|
||||
link: "/services/resource-augmentation",
|
||||
linkText: "Explore Augmentation",
|
||||
},
|
||||
{
|
||||
title: "OBSE - Bank Statement Extractor",
|
||||
icon: FaShippingFast,
|
||||
description:
|
||||
"Automate bank statement processing with OBSE. Our intelligent OCR solution extracts data accurately and quickly, enhancing efficiency, reducing risk, and speeding up financial assessments.",
|
||||
imageUrl: "/images/obse.svg", // Reusing image from OBSE
|
||||
link: "/obse",
|
||||
linkText: "Learn About OBSE",
|
||||
},
|
||||
];
|
||||
|
||||
const ourApproachItems = [
|
||||
{
|
||||
icon: FaProjectDiagram,
|
||||
title: "Strategic Alignment",
|
||||
description: "Understanding your goals to ensure solutions deliver value.",
|
||||
},
|
||||
{
|
||||
icon: FaHandshake,
|
||||
title: "Client-Centric Collaboration",
|
||||
description: "Working as true partners, involving you throughout.",
|
||||
},
|
||||
{
|
||||
icon: FaCogs,
|
||||
title: "Agile & Adaptive Delivery",
|
||||
description: "Flexible methodologies for faster, relevant results.",
|
||||
},
|
||||
{
|
||||
icon: FaWrench,
|
||||
title: "Technical Excellence",
|
||||
description: "Building robust, scalable, and maintainable solutions.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ServicesPage() {
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200 font-poppins">
|
||||
{/* 1. Hero Section */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-900 text-white py-20 md:py-32">
|
||||
<div className="absolute inset-0 bg-black opacity-50 dark:opacity-70"></div>
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
<h1
|
||||
className="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Tailored IT Services & Solutions
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-4xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 mb-8">
|
||||
Owethu Managed Services empowers businesses through innovative
|
||||
technology solutions, strategic consulting, flexible resource
|
||||
augmentation, and specialized product offerings.
|
||||
</p>
|
||||
<Link
|
||||
href="#core-services"
|
||||
className="inline-flex items-center justify-center bg-gold-500 text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 transition-colors duration-300"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
Explore Our Services <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. Core Service Areas Section */}
|
||||
<section
|
||||
id="core-services"
|
||||
className="py-16 md:py-24 bg-white dark:bg-gray-900"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Our Expertise, Your Advantage
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
We offer a comprehensive suite of services designed to address
|
||||
your specific IT needs and drive business growth.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{serviceAreas.map((service) => (
|
||||
<div
|
||||
key={service.title}
|
||||
className="flex flex-col bg-gray-50 dark:bg-gray-800 rounded-lg shadow-md overflow-hidden transform transition duration-300 hover:shadow-xl hover:-translate-y-1"
|
||||
>
|
||||
<div className="relative h-48 w-full">
|
||||
<Image
|
||||
src={service.imageUrl}
|
||||
alt={`${service.title} illustration`}
|
||||
layout="fill"
|
||||
objectFit="contain" // Changed to contain for SVG/Illustrations
|
||||
className="p-4 bg-gray-100 dark:bg-gray-700 z-10" // Added padding and bg
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6 flex flex-col flex-grow">
|
||||
<div className="flex items-center mb-3">
|
||||
<service.icon
|
||||
className="text-2xl mr-3 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h3 className="text-xl font-semibold font-poppins text-gray-900 dark:text-white">
|
||||
{service.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm mb-4 flex-grow">
|
||||
{service.description}
|
||||
</p>
|
||||
<Link
|
||||
href={service.link}
|
||||
className="inline-flex items-center mt-auto text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300 text-sm"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
{service.linkText} <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Our Approach Section */}
|
||||
<section className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
How We Deliver Excellence
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
Our methodology ensures collaboration, precision, and impactful
|
||||
results tailored to your project needs.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{ourApproachItems.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-2 hover:shadow-xl dark:hover:shadow-gold-500/20"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl text-gold-500 mx-auto mb-5"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-3 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Industry Expertise Snippet (Optional) */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaBriefcase
|
||||
className="text-5xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Serving Diverse Industries
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-8">
|
||||
We apply our technical expertise across various sectors,
|
||||
understanding the unique challenges and opportunities within each.
|
||||
Key areas include Financial Services, Automotive, Technology, and
|
||||
more.
|
||||
</p>
|
||||
<Link
|
||||
href="/about#industry-expertise" // Assuming you add an ID to the section in about page
|
||||
className="text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300 inline-flex items-center"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Learn More About Our Industry Focus{" "}
|
||||
<FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Call to Action (CTA) Section */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-gold-500 text-gray-900"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
Let's Build Your Solution
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed mb-8 font-poppins text-gray-800">
|
||||
Ready to discuss how OMS can help you achieve your technology goals?
|
||||
Contact us today for a consultation.
|
||||
</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300"
|
||||
>
|
||||
Get in Touch
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -12,48 +12,39 @@ import WhyChooseUsSection, {
|
||||
import FeaturedProductSection, {
|
||||
defaultObseFeatures,
|
||||
} from "./_components/FeaturedProductSection";
|
||||
// import HeroSectionModern from "./_components/HeroSectionModern";
|
||||
// import HeroSectionDynamic from "./_components/HeroSectionDynamic";
|
||||
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();
|
||||
|
||||
// Handle case where data might be null or undefined
|
||||
if (!data) {
|
||||
// Optionally return a loading state or default content
|
||||
return <div>Loading hero section...</div>;
|
||||
// Or render the HeroSection with default props if preferred
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
{/*
|
||||
<HeroSectionDynamic
|
||||
title={<>Where Innovation Meets Excellence</>} // Simplified title for this layout
|
||||
subtitle="We deliver cutting-edge IT solutions, empowering businesses to thrive in the ever-evolving digital landscape with unmatched industry expertise."
|
||||
buttonText="Explore Solutions" // Changed button text slightly
|
||||
buttonHref="/services" // Point to services maybe?
|
||||
imageUrl="/hero-bg.jpg" // Use a different, high-quality background image
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<HeroSectionModern
|
||||
title={
|
||||
<>
|
||||
Where Innovation <br className="hidden md:block" /> Meets
|
||||
Excellence.
|
||||
</>
|
||||
}
|
||||
subtitle="Welcome to Owethu Managed Services. We deliver cutting-edge IT solutions, empowering businesses to thrive in the ever-evolving digital landscape with unmatched industry expertise."
|
||||
buttonText="Learn More"
|
||||
buttonHref="/about"
|
||||
imageUrl="/hero-bg.jpg" // Specify your hero image
|
||||
/>
|
||||
*/}
|
||||
<HeroSection
|
||||
title={
|
||||
<>
|
||||
Where Innovation <br className="hidden md:block" /> Meets
|
||||
Excellence.
|
||||
{data?.hero_title} <br className="hidden md:block" />
|
||||
</>
|
||||
}
|
||||
subtitle="Welcome to Owethu Managed Services. We deliver cutting-edge IT solutions, empowering businesses to thrive in the ever-evolving digital landscape with unmatched industry expertise."
|
||||
buttonText="Learn More"
|
||||
buttonHref="/about"
|
||||
imageUrl="/hero-bg.jpg" // Specify your hero image
|
||||
subtitle={
|
||||
data.hero_subtitle || "Your trusted partner in technology solutions."
|
||||
} // Use optional chaining and provide a default
|
||||
buttonText={data.hero_buttons?.[0]?.label || "Learn More"} // Use optional chaining and provide a default
|
||||
buttonHref={data.hero_buttons?.[0]?.link || "/about"} // Use optional chaining and provide a default
|
||||
imageUrl={
|
||||
// data.hero_cover
|
||||
// ? `${process.env.DIRECTUS_API_ENDPOINT}/assets/${data.hero_cover}`
|
||||
// : "/hero-bg-2.jpg"
|
||||
"/banner-1.jpeg"
|
||||
} // Use optional chaining and provide a default
|
||||
/>
|
||||
<CoreServicesSection
|
||||
title="Core Services"
|
||||
@ -69,11 +60,11 @@ export default function HomePage() {
|
||||
eyebrow="Featured Product"
|
||||
title="Streamline Financial Analysis with"
|
||||
productName="OBSE"
|
||||
description="Our advanced Optical Bank Statement Extractor automates data aggregation, reduces errors, and provides deep financial insights."
|
||||
description="Our advanced Optimized Bank Statement Extractor automates data aggregation, reduces errors, and provides deep financial insights."
|
||||
features={defaultObseFeatures}
|
||||
buttonText="Learn More & Demo"
|
||||
buttonHref="/products/obse" // Link to the OBSE product page
|
||||
imageUrl="/obse-mockup.png" // **IMPORTANT: Create or find a relevant image**
|
||||
buttonHref="/obse" // Link to the OBSE product page
|
||||
imageUrl="/images/obse.svg" // **IMPORTANT: Create or find a relevant image**
|
||||
imageAlt="OBSE Product Interface Mockup"
|
||||
/>
|
||||
<ClientLogosSection
|
||||
|
||||
37
app/(website)/portal/page.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { Metadata } from "next";
|
||||
import { FaTools } from "react-icons/fa"; // Using a construction/tools icon
|
||||
import { COLORS } from "@/constants";
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Recruitment Portal | OMS South Africa",
|
||||
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() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-200px)] bg-gray-50 dark:bg-gray-800 text-center px-4 py-16 font-poppins">
|
||||
<FaTools
|
||||
className="text-6xl mb-6"
|
||||
style={{ color: COLORS.primary }} // Use primary color
|
||||
/>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Recruitment Portal Coming Soon!
|
||||
</h1>
|
||||
<p className="text-lg text-gray-700 dark:text-gray-300 max-w-xl mx-auto mb-8">
|
||||
We are currently building our dedicated recruitment portal to connect
|
||||
talented individuals with exciting career opportunities at OMS.
|
||||
</p>
|
||||
<p className="text-md text-gray-600 dark:text-gray-400">
|
||||
Please check back later for updates.
|
||||
</p>
|
||||
{/* Optional: Link back to home or contact */}
|
||||
{/* <Link href="/" className="mt-8 inline-block bg-gold-500 text-gray-900 font-bold py-2 px-6 rounded-md hover:bg-gold-600 transition-colors duration-300" style={{ backgroundColor: COLORS.primary }}>
|
||||
Go to Homepage
|
||||
</Link> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
286
app/(website)/services/page.tsx
Normal file
@ -0,0 +1,286 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
FaArrowRight,
|
||||
FaBriefcase,
|
||||
FaCogs,
|
||||
FaHandshake,
|
||||
FaLaptopCode,
|
||||
FaProjectDiagram,
|
||||
FaUsers,
|
||||
FaWrench,
|
||||
FaShippingFast,
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants"; // Assuming COLORS constant is available
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"Explore IT services by Owethu Managed Services (OMS): Custom Software Development, IT Resource Augmentation, and the OBSE Bank Statement Extractor. Partner with us for innovative solutions.",
|
||||
keywords: [
|
||||
"IT services South Africa",
|
||||
"custom software development",
|
||||
"IT resource augmentation",
|
||||
"managed IT services",
|
||||
"OBSE",
|
||||
"CVEvolve",
|
||||
"bank statement extractor",
|
||||
"bank statement automation",
|
||||
"fintech solutions",
|
||||
"IT consulting",
|
||||
"OMS",
|
||||
"Owethu Managed Services",
|
||||
"Centurion",
|
||||
"Gauteng",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Comprehensive IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"OMS offers tailored IT solutions including custom development, resource augmentation, and specialized products like OBSE.",
|
||||
url: "https://oms.africa/services", // Update with the final URL
|
||||
images: [
|
||||
{
|
||||
url: "/images/oms-services-og.png", // Replace with an appropriate OG image URL
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "OMS IT Services",
|
||||
},
|
||||
],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Comprehensive IT Services & Solutions | OMS South Africa",
|
||||
description:
|
||||
"Partner with OMS for expert IT services, from custom builds to resource scaling.",
|
||||
// images: ['/images/oms-services-twitter.png'], // Replace if needed
|
||||
},
|
||||
};
|
||||
|
||||
// Data for Service Areas
|
||||
const serviceAreas = [
|
||||
{
|
||||
title: "Custom Software Development & Consulting",
|
||||
icon: FaLaptopCode,
|
||||
description:
|
||||
"We design, build, and implement bespoke software solutions tailored to your unique business challenges and objectives. Our consulting services help align technology with your strategic goals.",
|
||||
imageUrl: "/images/team.svg", // Reusing image from About
|
||||
link: "/contact?subject=Custom Development Inquiry", // Link to contact or a future dedicated page
|
||||
linkText: "Discuss Your Project",
|
||||
},
|
||||
{
|
||||
title: "IT Resource Augmentation",
|
||||
icon: FaUsers,
|
||||
description:
|
||||
"Scale your team effectively with our flexible resource augmentation model. Access skilled IT professionals (BAs, Testers, Developers, Designers) on demand or via managed milestone-based projects.",
|
||||
imageUrl: "/images/team-collaborative.png", // Reusing image from Resource Augmentation
|
||||
link: "/services/resource-augmentation",
|
||||
linkText: "Explore Augmentation",
|
||||
},
|
||||
{
|
||||
title: "OBSE - Bank Statement Extractor",
|
||||
icon: FaShippingFast,
|
||||
description:
|
||||
"Automate bank statement processing with OBSE. Our intelligent OCR solution extracts data accurately and quickly, enhancing efficiency, reducing risk, and speeding up financial assessments.",
|
||||
imageUrl: "/images/obse.svg", // Reusing image from OBSE
|
||||
link: "/obse",
|
||||
linkText: "Learn About OBSE",
|
||||
},
|
||||
];
|
||||
|
||||
const ourApproachItems = [
|
||||
{
|
||||
icon: FaProjectDiagram,
|
||||
title: "Strategic Alignment",
|
||||
description: "Understanding your goals to ensure solutions deliver value.",
|
||||
},
|
||||
{
|
||||
icon: FaHandshake,
|
||||
title: "Client-Centric Collaboration",
|
||||
description: "Working as true partners, involving you throughout.",
|
||||
},
|
||||
{
|
||||
icon: FaCogs,
|
||||
title: "Agile & Adaptive Delivery",
|
||||
description: "Flexible methodologies for faster, relevant results.",
|
||||
},
|
||||
{
|
||||
icon: FaWrench,
|
||||
title: "Technical Excellence",
|
||||
description: "Building robust, scalable, and maintainable solutions.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ServicesPage() {
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200 font-poppins">
|
||||
{/* 1. Hero Section */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-900 text-white py-20 md:py-32">
|
||||
<div className="absolute inset-0 bg-black opacity-50 dark:opacity-70"></div>
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
<h1
|
||||
className="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Tailored IT Services & Solutions
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-4xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 mb-8">
|
||||
Owethu Managed Services empowers businesses through innovative
|
||||
technology solutions, strategic consulting, flexible resource
|
||||
augmentation, and specialized product offerings.
|
||||
</p>
|
||||
<Link
|
||||
href="#core-services"
|
||||
className="inline-flex items-center justify-center bg-gold-500 text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 transition-colors duration-300"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
Explore Our Services <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. Core Service Areas Section */}
|
||||
<section
|
||||
id="core-services"
|
||||
className="py-16 md:py-24 bg-white dark:bg-gray-900"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Our Expertise, Your Advantage
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
We offer a comprehensive suite of services designed to address
|
||||
your specific IT needs and drive business growth.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{serviceAreas.map((service) => (
|
||||
<div
|
||||
key={service.title}
|
||||
className="flex flex-col bg-gray-50 dark:bg-gray-800 rounded-lg shadow-md overflow-hidden transform transition duration-300 hover:shadow-xl hover:-translate-y-1"
|
||||
>
|
||||
<div className="relative h-48 w-full">
|
||||
<Image
|
||||
src={service.imageUrl}
|
||||
alt={`${service.title} illustration`}
|
||||
layout="fill"
|
||||
objectFit="contain" // Changed to contain for SVG/Illustrations
|
||||
className="p-4 bg-gray-100 dark:bg-gray-700" // Added padding and bg
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6 flex flex-col flex-grow">
|
||||
<div className="flex items-center mb-3">
|
||||
<service.icon
|
||||
className="text-2xl mr-3 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h3 className="text-xl font-semibold font-poppins text-gray-900 dark:text-white">
|
||||
{service.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm mb-4 flex-grow">
|
||||
{service.description}
|
||||
</p>
|
||||
<Link
|
||||
href={service.link}
|
||||
className="inline-flex items-center mt-auto text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300 text-sm"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
{service.linkText} <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Our Approach Section */}
|
||||
<section className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
How We Deliver Excellence
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto font-poppins">
|
||||
Our methodology ensures collaboration, precision, and impactful
|
||||
results tailored to your project needs.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{ourApproachItems.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-2 hover:shadow-xl dark:hover:shadow-gold-500/20"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl text-gold-500 mx-auto mb-5"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-3 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 font-poppins text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Industry Expertise Snippet (Optional) */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaBriefcase
|
||||
className="text-5xl text-gold-500 mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Serving Diverse Industries
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-8">
|
||||
We apply our technical expertise across various sectors,
|
||||
understanding the unique challenges and opportunities within each.
|
||||
Key areas include Financial Services, Automotive, Technology, and
|
||||
more.
|
||||
</p>
|
||||
<Link
|
||||
href="/about#industry-expertise" // Assuming you add an ID to the section in about page
|
||||
className="text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300 inline-flex items-center"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Learn More About Our Industry Focus{" "}
|
||||
<FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Call to Action (CTA) Section */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-gold-500 text-gray-900"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
Let's Build Your Solution
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed mb-8 font-poppins text-gray-800">
|
||||
Ready to discuss how OMS can help you achieve your technology goals?
|
||||
Contact us today for a consultation.
|
||||
</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300"
|
||||
>
|
||||
Get in Touch
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
618
app/(website)/services/product-development/page.tsx
Normal file
@ -0,0 +1,618 @@
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
FaArrowRight,
|
||||
FaDraftingCompass,
|
||||
FaHandshake,
|
||||
FaHeadset,
|
||||
FaLaptopCode, // For Web App Dev, Backend
|
||||
FaLightbulb, // For Ideation, Innovation
|
||||
FaPuzzlePiece, // For Custom Software Solutions
|
||||
FaRocket, // For Product Launch, Scalable Tech
|
||||
FaSearch, // For User Research
|
||||
FaShieldAlt, // For Security Testing
|
||||
FaSyncAlt, // For Agile, Iteration
|
||||
FaUserCheck, // For User-Centred Design, Usability Testing
|
||||
FaUsersCog, // For Dedicated Teams
|
||||
// Additional icons that might be useful for the new content:
|
||||
FaMobileAlt, // For Mobile Apps
|
||||
FaDesktop, // For Desktop Apps
|
||||
FaBrain, // For AI & ML
|
||||
FaCubes, // For Blockchain (alternative: FaLink)
|
||||
FaNetworkWired, // For IoT (alternative: FaBroadcastTower)
|
||||
FaBullseye, // For Market Strategy
|
||||
FaChartLine, // For Growth/Scalability
|
||||
FaTasks, // For QA
|
||||
FaComments, // For Feedback
|
||||
//FaPhone,
|
||||
//FaEnvelope,
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants";
|
||||
//import ContactForm from "@/components/ContactForm";
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Product Development Services | Digital Solutions by OMS",
|
||||
description:
|
||||
"OMS delivers innovative, tailored digital solutions. Explore our end-to-end product development services, from ideation to launch and beyond.",
|
||||
keywords: [
|
||||
"product development",
|
||||
"custom software solutions",
|
||||
"user-centred design",
|
||||
"agile development",
|
||||
"cross-platform solutions",
|
||||
"mobile app development",
|
||||
"web app development",
|
||||
"tech stack",
|
||||
"quality assurance",
|
||||
"OMS",
|
||||
"Owethu Management Services",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Product Development Services | Digital Solutions by OMS",
|
||||
description:
|
||||
"Transform your ideas into market-ready products with OMS's comprehensive development expertise.",
|
||||
url: "https://www.oms.africa/services/product-development", // Replace with your actual URL
|
||||
images: [
|
||||
{
|
||||
url: "/images/product-dev-og-image.png", // Replace with an appropriate OG image URL
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "OMS Product Development Services",
|
||||
},
|
||||
],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Product Development Services | Digital Solutions by OMS",
|
||||
description:
|
||||
"Build innovative digital products with OMS. We cover the full lifecycle from concept to continuous improvement.",
|
||||
// images: ['/images/product-dev-twitter-image.png'], // Replace if needed
|
||||
},
|
||||
};
|
||||
|
||||
interface FeatureItem {
|
||||
icon?: React.ElementType; // Icon is optional for some lists like tech stack
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const SectionTitle: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4 text-center">
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
const SectionParagraph: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto text-center mb-10 md:mb-12">
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
const FeatureGrid: React.FC<{ items: FeatureItem[]; iconColor?: string }> = ({
|
||||
items,
|
||||
iconColor = COLORS.primary,
|
||||
}) => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md"
|
||||
>
|
||||
{item.icon && (
|
||||
<item.icon className="text-3xl mb-3" style={{ color: iconColor }} />
|
||||
)}
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function ProductDevelopmentPage() {
|
||||
const howOmsDeliversItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaPuzzlePiece,
|
||||
title: "Custom Software Solutions",
|
||||
description:
|
||||
"We craft bespoke applications designed to solve your unique business challenges, ensuring alignment with your goals and processes.",
|
||||
},
|
||||
{
|
||||
icon: FaUserCheck,
|
||||
title: "User-Centred Design",
|
||||
description:
|
||||
"We prioritise seamless functionality with an intuitive user experience, creating products that are not only powerful but also easy to use.",
|
||||
},
|
||||
{
|
||||
icon: FaRocket,
|
||||
title: "Scalable & Future-Proof Technology",
|
||||
description:
|
||||
"Our solutions are built with long-term growth in mind, ensuring adaptability to future demands and changes in technology.",
|
||||
},
|
||||
{
|
||||
icon: FaSyncAlt,
|
||||
title: "Agile Development Approach",
|
||||
description:
|
||||
"Through iterative development, we ensure faster time-to-market, with continuous feedback from users driving improvements along the way.",
|
||||
},
|
||||
];
|
||||
|
||||
const endToEndItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaLightbulb,
|
||||
title: "Ideation & Concept Development",
|
||||
description:
|
||||
"We collaborate with you to transform your ideas into a clear product vision and roadmap.",
|
||||
},
|
||||
{
|
||||
icon: FaDraftingCompass,
|
||||
title: "Prototyping & MVP Development",
|
||||
description:
|
||||
"We build prototypes and minimum viable products to validate concepts early, gather feedback, and ensure alignment with user needs.",
|
||||
},
|
||||
{
|
||||
icon: FaBullseye,
|
||||
title: "Product Launch & Market Strategy",
|
||||
description:
|
||||
"Our team helps with the go-to-market strategy, ensuring a successful product launch with strong market positioning and customer adoption.",
|
||||
},
|
||||
{
|
||||
icon: FaHeadset,
|
||||
title: "Post-Launch Support & Continuous Improvement",
|
||||
description:
|
||||
"We provide ongoing support, monitoring, and iterative enhancements based on user feedback and market trends.",
|
||||
},
|
||||
];
|
||||
|
||||
const crossPlatformItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaMobileAlt,
|
||||
title: "Mobile Application Development",
|
||||
description:
|
||||
"We create intuitive mobile apps for both iOS and Android, ensuring seamless performance and a high-quality user experience.",
|
||||
},
|
||||
{
|
||||
icon: FaLaptopCode,
|
||||
title: "Web Application Development",
|
||||
description:
|
||||
"Tailored web applications that are scalable, secure, and optimized for the best user interaction.",
|
||||
},
|
||||
{
|
||||
icon: FaDesktop,
|
||||
title: "Desktop Applications",
|
||||
description:
|
||||
"For businesses requiring more complex or resource-heavy solutions, we build powerful desktop applications with smooth integration.",
|
||||
},
|
||||
];
|
||||
|
||||
const techStackItems: FeatureItem[] = [
|
||||
{ title: "Frontend Development", description: "React, Angular, Vue.js" },
|
||||
{
|
||||
title: "Backend Development",
|
||||
description: "Node.js, Python, Java, Ruby on Rails",
|
||||
},
|
||||
{
|
||||
title: "Database Technologies",
|
||||
description: "MySQL, PostgreSQL, MongoDB, NoSQL",
|
||||
},
|
||||
{
|
||||
title: "Cloud Services & DevOps",
|
||||
description: "AWS, Azure, Google Cloud Platform",
|
||||
},
|
||||
{
|
||||
title: "AI & Machine Learning",
|
||||
description: "TensorFlow, PyTorch, Scikit-learn",
|
||||
},
|
||||
];
|
||||
|
||||
const qualityAssuranceItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaTasks,
|
||||
title: "Manual & Automated Testing",
|
||||
description:
|
||||
"Comprehensive testing to ensure the product meets all requirements and functions seamlessly.",
|
||||
},
|
||||
{
|
||||
icon: FaChartLine, // Or FaTachometerAlt if you add it
|
||||
title: "Performance & Load Testing",
|
||||
description:
|
||||
"We simulate high traffic and performance scenarios to ensure your product can scale and handle user demands.",
|
||||
},
|
||||
{
|
||||
icon: FaShieldAlt,
|
||||
title: "Security Testing",
|
||||
description:
|
||||
"Proactive identification and mitigation of potential security vulnerabilities to safeguard your product and user data.",
|
||||
},
|
||||
];
|
||||
|
||||
const collaborativeProcessItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaSyncAlt,
|
||||
title: "Agile Methodology",
|
||||
description:
|
||||
"Our flexible and iterative approach ensures that your product evolves based on real-time feedback, reducing risk and accelerating time-to-market.",
|
||||
},
|
||||
{
|
||||
icon: FaHandshake,
|
||||
title: "Client Involvement",
|
||||
description:
|
||||
"Regular check-ins, demos, and sprint reviews keep you involved and aligned with project progress, making sure we are building the product you envisioned.",
|
||||
},
|
||||
{
|
||||
icon: FaUsersCog,
|
||||
title: "Dedicated Development Teams",
|
||||
description:
|
||||
"Our dedicated teams work closely with your business to ensure that every decision aligns with your strategic goals and product vision.",
|
||||
},
|
||||
];
|
||||
|
||||
const userCentredApproachItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaSearch,
|
||||
title: "User Research & Personas",
|
||||
description:
|
||||
"We conduct thorough user research to understand the needs, behaviours, and challenges of your target audience.",
|
||||
},
|
||||
{
|
||||
icon: FaUserCheck,
|
||||
title: "Usability Testing",
|
||||
description:
|
||||
"Regular usability tests are conducted to ensure that your product is easy to navigate, efficient, and enjoyable to use.",
|
||||
},
|
||||
{
|
||||
icon: FaComments,
|
||||
title: "User Feedback & Iteration",
|
||||
description:
|
||||
"We continuously gather user feedback and iterate on product features to improve usability and customer satisfaction.",
|
||||
},
|
||||
];
|
||||
|
||||
const innovationItems: FeatureItem[] = [
|
||||
{
|
||||
icon: FaBrain,
|
||||
title: "Artificial Intelligence & Machine Learning",
|
||||
description:
|
||||
"We integrate AI and machine learning to create smarter, data-driven products that can adapt and scale as user needs evolve.",
|
||||
},
|
||||
{
|
||||
icon: FaCubes,
|
||||
title: "Blockchain Solutions",
|
||||
description:
|
||||
"We incorporate blockchain for secure, transparent, and decentralized applications.",
|
||||
},
|
||||
{
|
||||
icon: FaNetworkWired,
|
||||
title: "Internet of Things (IoT)",
|
||||
description:
|
||||
"We build IoT-enabled products that connect the physical and digital worlds for enhanced business intelligence and automation.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200 font-poppins">
|
||||
{/* 1. Hero Section */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800 text-white py-24 md:py-40">
|
||||
<div className="absolute inset-0 bg-black opacity-40 dark:opacity-60"></div>
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
<h1
|
||||
className="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Product Development
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-4xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 mb-8">
|
||||
At OMS, we don't just create products—we build digital
|
||||
solutions that accelerate business growth and transform industries.
|
||||
We understand that great products are the result of careful
|
||||
planning, technical expertise, and a deep understanding of user
|
||||
needs. From concept to launch, our product development services
|
||||
deliver innovative, tailored solutions that help businesses enhance
|
||||
efficiency, reduce complexity, and unlock new revenue streams.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="#how-we-deliver"
|
||||
className="inline-flex items-center justify-center bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 transition-colors duration-300"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
How We Deliver
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex items-center text-gold-400 hover:text-gold-300 transition-colors duration-300"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Discuss Your Project <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. How OMS Delivers Market-Ready Products Section */}
|
||||
<section
|
||||
id="how-we-deliver"
|
||||
className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>How OMS Delivers Market-Ready Products</SectionTitle>
|
||||
{/* No intro paragraph provided for this specific subtitle in the source */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto">
|
||||
{howOmsDeliversItems.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-4 rounded-lg shadow-md"
|
||||
>
|
||||
{item.icon && (
|
||||
<item.icon
|
||||
className="text-3xl mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
)}
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. End-to-End Product Development Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>End-to-End Product Development</SectionTitle>
|
||||
<SectionParagraph>
|
||||
We support businesses throughout the entire product lifecycle, from
|
||||
ideation to post-launch support. Our comprehensive services cover
|
||||
every phase of development, ensuring that your product is not only
|
||||
designed and built to meet your current needs, but is also ready for
|
||||
future growth and evolution.
|
||||
</SectionParagraph>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto">
|
||||
{endToEndItems.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-4 rounded-lg shadow-md"
|
||||
>
|
||||
{item.icon && (
|
||||
<item.icon
|
||||
className="text-3xl mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
)}
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* <FeatureGrid items={endToEndItems} /> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Cross-Platform Solutions Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>Cross-Platform Solutions</SectionTitle>
|
||||
<SectionParagraph>
|
||||
We deliver cross-platform products that work seamlessly across web,
|
||||
mobile, and desktop environments. This ensures your product is
|
||||
accessible to a wider audience and provides a consistent user
|
||||
experience, regardless of the device or platform.
|
||||
</SectionParagraph>
|
||||
<FeatureGrid items={crossPlatformItems} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Tech Stack Expertise Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>Tech Stack Expertise</SectionTitle>
|
||||
<SectionParagraph>
|
||||
Our product development solutions are powered by a diverse range of
|
||||
technologies. Whether you need a high-performance web app, a mobile
|
||||
solution, or a complex enterprise application, our team uses the
|
||||
latest and most suitable technologies to ensure the product is
|
||||
reliable, secure, and future-ready.
|
||||
</SectionParagraph>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{techStackItems.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-gray-100 dark:bg-gray-800 p-6 rounded-lg shadow-sm"
|
||||
>
|
||||
<h4
|
||||
className="text-lg font-semibold mb-2 font-poppins text-gold-600 dark:text-gold-400"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 6. Quality Assurance & Testing Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>Quality Assurance & Testing</SectionTitle>
|
||||
<SectionParagraph>
|
||||
Our product development approach includes robust testing and quality
|
||||
assurance to ensure that your product is error-free, secure, and
|
||||
optimized for performance. We conduct thorough testing at each stage
|
||||
of the development process to deliver a high-quality final product.
|
||||
</SectionParagraph>
|
||||
<FeatureGrid items={qualityAssuranceItems} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 7. Collaborative Development Process Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>Collaborative Development Process</SectionTitle>
|
||||
<SectionParagraph>
|
||||
We believe that collaboration is key to successful product
|
||||
development. Our agile and transparent development process ensures
|
||||
that you are always in the loop, from initial discussions to final
|
||||
delivery.
|
||||
</SectionParagraph>
|
||||
<FeatureGrid items={collaborativeProcessItems} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 8. User-Centred Approach Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>User-Centred Approach</SectionTitle>
|
||||
<SectionParagraph>
|
||||
We design products with the end-user in mind, focusing on creating
|
||||
intuitive, engaging, and valuable experiences that delight customers
|
||||
and drive retention.
|
||||
</SectionParagraph>
|
||||
<FeatureGrid items={userCentredApproachItems} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 9. Innovation & Emerging Technologies Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<SectionTitle>Innovation & Emerging Technologies</SectionTitle>
|
||||
<SectionParagraph>
|
||||
At OMS, we believe in staying ahead of the curve by leveraging the
|
||||
latest innovations and emerging technologies to build
|
||||
next-generation products. We are constantly exploring new ways to
|
||||
incorporate cutting-edge solutions into our product development
|
||||
process.
|
||||
</SectionParagraph>
|
||||
<FeatureGrid items={innovationItems} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 10. Call to Action (CTA) Section */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
Ready to Transform Your Ideas into Reality?
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed mb-8 font-poppins text-gray-800">
|
||||
Let's discuss your product vision and explore how OMS can
|
||||
develop innovative digital solutions to drive your business forward.
|
||||
Schedule a consultation with our experts today.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="/contact" // Pre-fill subject
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300"
|
||||
>
|
||||
Request a Consultation
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact" // Link to contact section below
|
||||
className="inline-block bg-transparent border-2 border-gray-800 text-gray-800 font-bold py-3 px-8 rounded-md hover:bg-gray-800 hover:text-white transition-colors duration-300"
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 11. Contact Information Section */}
|
||||
{/* <section
|
||||
id="contact"
|
||||
className="py-16 md:py-24 bg-gray-100 dark:bg-gray-800"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Have a project in mind or need more information about our product
|
||||
development services? Reach out today.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<FaPhone
|
||||
className="w-6 h-6 text-gold-500 dark:text-gold-400 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Phone
|
||||
</h3>
|
||||
<a
|
||||
href="tel:+27120513281"
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm"
|
||||
>
|
||||
(012) 051 3281
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<FaEnvelope
|
||||
className="w-6 h-6 text-gold-500 dark:text-gold-400 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold font-poppins text-gray-800 dark:text-gray-100">
|
||||
Email
|
||||
</h3>
|
||||
<a
|
||||
href="mailto:info@oms.africa?subject=Product Development Inquiry"
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100 transition text-sm"
|
||||
>
|
||||
info@oms.africa
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex items-center text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Go to Full Contact Page <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md">
|
||||
<h3 className="text-xl font-semibold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Send Us Your Inquiry
|
||||
</h3>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section> */}
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
840
app/(website)/services/resource-augmentation/page.tsx
Normal file
@ -0,0 +1,840 @@
|
||||
// /app/resource-augmentation/page.tsx (or wherever you place your page components)
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
FaArrowRight,
|
||||
FaCheckCircle,
|
||||
FaClipboardList, // For BA
|
||||
FaClock, // For Time & Material
|
||||
FaCode, // For Development (General)
|
||||
FaUsersCog, // For Managed Services/Teams
|
||||
FaPaintBrush, // For UX/UI Design
|
||||
FaProjectDiagram, // For Architecture/Milestones
|
||||
FaPuzzlePiece, // For Integration/Solutions
|
||||
FaShieldAlt, // For Reliability/QA
|
||||
FaSitemap, // For Process/Architecture
|
||||
FaSyncAlt, // For Agile/Flexibility
|
||||
FaTasks, // For Project Management/Milestones
|
||||
FaVial, // For Testing
|
||||
FaChartLine, // For Reporting/MI
|
||||
FaTools, // General Capabilities
|
||||
FaLayerGroup, // Frameworks (SAFe, etc.)
|
||||
FaFileInvoiceDollar, // Cost/Pricing
|
||||
FaBusinessTime, // Experience/Past Projects
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants"; // Assuming COLORS constant is available
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "IT Resource Augmentation & Managed Services | OMS South Africa",
|
||||
description:
|
||||
"Flexible IT resource augmentation by Owethu Managed Services (OMS). Access expert Business Analysts, Developers, Testers, UX/UI Designers, and more on-demand or via milestone-based projects. Centurion, Gauteng.",
|
||||
keywords: [
|
||||
"IT resource augmentation",
|
||||
"managed IT services",
|
||||
"IT staffing South Africa",
|
||||
"project resources",
|
||||
"IT project delivery",
|
||||
"Business Analyst",
|
||||
"Software Developer",
|
||||
"Test Analyst",
|
||||
"UX/UI Designer",
|
||||
"Process Engineer",
|
||||
"Salesforce Partner",
|
||||
// "time and material IT",
|
||||
"milestone-based projects",
|
||||
"IT outsourcing",
|
||||
"flexible IT resources",
|
||||
"Centurion",
|
||||
"Gauteng",
|
||||
"OMS",
|
||||
"Owethu Managed Services",
|
||||
"black female owned IT",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Expert IT Resource Augmentation Services | OMS South Africa",
|
||||
description:
|
||||
"Scale your IT projects effectively with OMS's flexible resource augmentation and managed services.",
|
||||
url: "https://oms.cvevolve.com/resource-augmentation", // Update with the final URL
|
||||
images: [
|
||||
{
|
||||
// Replace with an appropriate OG image URL if you have one
|
||||
url: "/images/oms-resource-augmentation-og.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "OMS IT Resource Augmentation Services",
|
||||
},
|
||||
],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Expert IT Resource Augmentation Services | OMS South Africa",
|
||||
description:
|
||||
"Get skilled IT professionals on demand. OMS offers flexible resource augmentation in South Africa.",
|
||||
// images: ['/images/oms-resource-augmentation-twitter.png'], // Replace if needed
|
||||
},
|
||||
};
|
||||
|
||||
// --- Data Structures from PDF ---
|
||||
|
||||
interface ServiceModel {
|
||||
icon: React.ElementType;
|
||||
title: string;
|
||||
description: string;
|
||||
details: string[];
|
||||
pricingModel: string;
|
||||
}
|
||||
|
||||
const serviceModels: ServiceModel[] = [
|
||||
{
|
||||
icon: FaClock,
|
||||
title: "Resource Augmentation Model",
|
||||
description:
|
||||
"Access skilled IT professionals on demand to supplement your existing team.",
|
||||
details: [
|
||||
"Provides required Technology and Human Resources.",
|
||||
"Client manages the project end-to-end.",
|
||||
"Ideal for adding specific skills or capacity quickly.",
|
||||
"Resources-on-Demand ensures no long-term FTE costs after project completion.",
|
||||
],
|
||||
pricingModel: "Time-and-Material pricing structure.",
|
||||
},
|
||||
{
|
||||
icon: FaTasks,
|
||||
title: "Milestone-Based Model",
|
||||
description:
|
||||
"OMS takes full responsibility for delivering specific IT project phases or entire projects.",
|
||||
details: [
|
||||
"Includes Project Planning & Management.",
|
||||
"Covers End-to-end Process Design (Requirements, Business Process Design, Architecture, User Experience/User Interface, Data Architecture, Transformation, and Business Intelligence).",
|
||||
"Change Management & Training ",
|
||||
"Comprehensive Testing and Quality Assurance.",
|
||||
],
|
||||
pricingModel:
|
||||
"Milestone-Based pricing, payable on delivery of key milestones.",
|
||||
},
|
||||
];
|
||||
|
||||
interface Capability {
|
||||
role: string;
|
||||
icon: React.ElementType;
|
||||
description: string;
|
||||
skills: {
|
||||
level: string; // e.g., 'Senior', 'Intermediate', 'Junior'
|
||||
points: string[];
|
||||
}[]; // Simplified for web display
|
||||
}
|
||||
|
||||
// Simplified representation of Page 4 for web display
|
||||
const capabilities: Capability[] = [
|
||||
{
|
||||
role: "Business / System Analysts",
|
||||
icon: FaClipboardList,
|
||||
description:
|
||||
"Bridge the gap between business needs and technology solutions, defining requirements and processes.",
|
||||
skills: [
|
||||
{
|
||||
level: "Senior",
|
||||
points: [
|
||||
"Stakeholder engagement & management",
|
||||
"Lead other BAs",
|
||||
"Requirements/Backlog management",
|
||||
"Process Design",
|
||||
"Report in Steering committees",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Intermediate",
|
||||
points: [
|
||||
"Document requirements",
|
||||
"Design solutions with architects",
|
||||
"Stakeholder engagement",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Junior",
|
||||
points: ["Elicit requirements", "Document requirements", "UML Design"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Test Analysts (Manual & Automation)",
|
||||
icon: FaVial,
|
||||
description:
|
||||
"Ensure software quality and reliability through rigorous testing strategies and execution.",
|
||||
skills: [
|
||||
{
|
||||
level: "Senior",
|
||||
points: [
|
||||
"Lead Test Analysts",
|
||||
"Develop Test Strategy",
|
||||
"Manage Test Execution",
|
||||
"UAT Training",
|
||||
"Stakeholder Management",
|
||||
"Report in Steering committees",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Intermediate",
|
||||
points: [
|
||||
"Document Test Scenarios",
|
||||
"Write test scripts",
|
||||
"Manage Test Execution",
|
||||
"Report progress",
|
||||
"Execute manual/automated testing",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Junior",
|
||||
points: [
|
||||
"Document Test Scenarios",
|
||||
"Execute manual/automated testing",
|
||||
"Produce Test Data",
|
||||
"Defects tracking",
|
||||
"UAT Training",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "UX / UI / CX Designers",
|
||||
icon: FaPaintBrush,
|
||||
description:
|
||||
"Craft intuitive, engaging, and effective user experiences and interfaces.",
|
||||
skills: [
|
||||
{
|
||||
level: "Senior",
|
||||
points: [
|
||||
"Develop Design Led Strategy",
|
||||
"Lead other designers",
|
||||
"Define User Journeys",
|
||||
"Perform user experience testing",
|
||||
"Stakeholder engagement",
|
||||
"Produce UI prototypes",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Intermediate",
|
||||
points: [
|
||||
"Define User Journeys",
|
||||
"Perform user experience testing",
|
||||
"Produce design progress reports",
|
||||
"Execute designs for UI prototypes",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Junior",
|
||||
points: [
|
||||
"Define User Journeys",
|
||||
"Produce design progress reports",
|
||||
"Execute designs for UI prototypes",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Process Engineers / Designers",
|
||||
icon: FaSitemap,
|
||||
description:
|
||||
"Analyze, design, and optimize business processes for efficiency and effectiveness.",
|
||||
skills: [
|
||||
{
|
||||
level: "Senior",
|
||||
points: [
|
||||
"Develop Process Design Strategy",
|
||||
"Manage and lead teams",
|
||||
"Define Target Operating Model",
|
||||
"Design ASIS/TOBE Processes",
|
||||
"Stakeholder management",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Intermediate",
|
||||
points: [
|
||||
"Design ASIS/TOBE Processes",
|
||||
"Stakeholder management",
|
||||
"Evaluate & remove inefficiencies",
|
||||
"Optimize current processes",
|
||||
],
|
||||
},
|
||||
{
|
||||
level: "Junior",
|
||||
points: [
|
||||
"Design ASIS/TOBE Processes",
|
||||
"Validate & remove inefficiencies",
|
||||
"Optimize current processes",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Note: Developer roles are mentioned in 'About Us' but not detailed in the table. Add a general entry if desired.
|
||||
{
|
||||
role: "Software Developers & Architects",
|
||||
icon: FaCode, // Or FaProjectDiagram for Architects
|
||||
description:
|
||||
"Build, implement, and architect robust and scalable software solutions.",
|
||||
skills: [
|
||||
{
|
||||
level: "General",
|
||||
points: [
|
||||
"Expertise across various modern technologies (backend, frontend, integration)",
|
||||
"Solution architecture design",
|
||||
"Development lifecycle management",
|
||||
"API development and integration",
|
||||
"Database design and management",
|
||||
],
|
||||
},
|
||||
], // Keep generic as PDF doesn't detail levels here
|
||||
},
|
||||
];
|
||||
|
||||
// interface Benefit {
|
||||
// icon: React.ElementType;
|
||||
// title: string;
|
||||
// description: string;
|
||||
// }
|
||||
|
||||
// const augmentationBenefits: Benefit[] = [
|
||||
// {
|
||||
// icon: FaSyncAlt,
|
||||
// title: "Ultimate Flexibility",
|
||||
// description:
|
||||
// "Scale your team up or down quickly based on project demands and budget.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaUsers,
|
||||
// title: "Access to Expertise",
|
||||
// description:
|
||||
// "Gain immediate access to specialized IT skills that may not be available in-house.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaFileInvoiceDollar,
|
||||
// title: "Cost-Effectiveness",
|
||||
// description:
|
||||
// "Reduce recruitment costs, overheads, and long-term commitments associated with FTEs.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaChartLine,
|
||||
// title: "Increased Productivity",
|
||||
// description:
|
||||
// "Focus your core team on strategic initiatives while OMS resources handle specific tasks or projects.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaShieldAlt,
|
||||
// title: "Reduced Hiring Burden",
|
||||
// description:
|
||||
// "Avoid the time-consuming process of sourcing, vetting, hiring, and onboarding new employees.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaTasks,
|
||||
// title: "Focus on Core Business",
|
||||
// description:
|
||||
// "Outsource IT project execution or specific roles, allowing you to concentrate on your primary objectives.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaCheckCircle,
|
||||
// title: "Quality Assurance",
|
||||
// description:
|
||||
// "Benefit from experienced professionals and managed delivery (in Milestone model) for high-quality outcomes.",
|
||||
// },
|
||||
// {
|
||||
// icon: FaLightbulb,
|
||||
// title: "Faster Project Delivery",
|
||||
// description:
|
||||
// "Accelerate project timelines by quickly filling skill gaps and adding capacity.",
|
||||
// },
|
||||
// ];
|
||||
|
||||
const methodologies = [
|
||||
{ name: "Agile (SCRUM)", icon: FaSyncAlt },
|
||||
{ name: "Scaled Agile (SAFe)", icon: FaLayerGroup },
|
||||
{ name: "Waterfall", icon: FaProjectDiagram }, // Using ProjectDiagram as a proxy
|
||||
];
|
||||
|
||||
const clientLogos = [
|
||||
{ src: "/images/absa.png", alt: "Absa Bank Logo" }, // Replace with actual paths
|
||||
{ src: "/images/sybrin.svg", alt: "Sybrin Logo" },
|
||||
{ src: "/images/bcx.png", alt: "BCX Logo" },
|
||||
{ src: "/images/sasol.png", alt: "Sasol Logo" },
|
||||
{ src: "/images/toyota-logo.png", alt: "Toyota Logo" },
|
||||
];
|
||||
|
||||
// --- Page Component ---
|
||||
|
||||
export default function ResourceAugmentationPage() {
|
||||
return (
|
||||
<div className="bg-white text-gray-800 overflow-x-hidden dark:bg-gray-900 dark:text-gray-200 font-poppins">
|
||||
{/* 1. Hero Section */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-900 text-white py-20 md:py-32">
|
||||
<div className="absolute inset-0 bg-black opacity-50 dark:opacity-70"></div>
|
||||
{/* Optional: Add a subtle background pattern or image */}
|
||||
{/* <div className="absolute inset-0 bg-[url('/images/team-background.jpg')] bg-cover bg-center opacity-10"></div> */}
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
{/* Consider adding OMS Logo here if desired */}
|
||||
{/* <Image src="/oms-logo-white.png" alt="OMS Logo" width={150} height={50} className="mx-auto mb-6" /> */}
|
||||
<h1
|
||||
|
||||
className="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md leading-<1.5>"
|
||||
style={{ color: COLORS.primary }} // Use gold color
|
||||
>
|
||||
Flexible IT Resource <br />Augmentation & Managed Services
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-4xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 mb-8">
|
||||
Scale your IT capabilities effectively with Owethu Managed Services.
|
||||
Access expert resources on demand or entrust us with full project
|
||||
delivery through our flexible engagement models.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="#service-models" // Link to the service models section
|
||||
className="inline-flex items-center justify-center bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900 font-bold py-3 px-8 rounded-md hover:bg-gold-600 transition-colors duration-300"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
Explore Our Models <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact?subject=Resource Augmentation Inquiry"
|
||||
className="inline-flex items-center text-gold-400 hover:text-gold-300 transition-colors duration-300 font-semibold"
|
||||
style={{ color: COLORS.primary }} // Use gold color for secondary CTA text too
|
||||
>
|
||||
Discuss Your Needs
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. The Need / Why Resource Augmentation? Section */}
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Bridge Your IT Skills Gap & Scale Efficiently
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Modern IT projects demand diverse skills and flexible capacity.
|
||||
Resource augmentation helps overcome common challenges faced by
|
||||
businesses today.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-8 items-stretch">
|
||||
{[
|
||||
{
|
||||
icon: FaPuzzlePiece,
|
||||
title: "Access Specialised Expertise",
|
||||
desc: "Get experienced professionals in software development, business analysis, cybersecurity, cloud computing, data science, and more — bringing the right skills to your project, exactly when you need them.",
|
||||
},
|
||||
{
|
||||
icon: FaChartLine,
|
||||
title: "Flexibility & Scalability",
|
||||
desc: "Easily scale your team in response to changing project needs — expanding or reducing resources without long-term obligations, while maintaining agility and control.",
|
||||
},
|
||||
{
|
||||
icon: FaClock,
|
||||
title: "Faster Onboarding & Deployment",
|
||||
desc: "Our experts are project-ready, helping you accelerate delivery timelines and reduce the overhead typically associated with recruitment and training.",
|
||||
},
|
||||
{
|
||||
icon: FaFileInvoiceDollar,
|
||||
title: "Cost-Effective Hiring",
|
||||
desc: "Maximize impact, minimize overhead — allowing you to scale your team according to project needs while managing costs efficiently and without compromising on quality. .",
|
||||
},
|
||||
/*
|
||||
{
|
||||
icon: FaBriefcase,
|
||||
title: "Focus on Core Business",
|
||||
desc: "Needing to free up internal teams to concentrate on strategic goals rather than specific project tasks.",
|
||||
},
|
||||
{
|
||||
icon: FaSyncAlt,
|
||||
title: "Need for Flexibility",
|
||||
desc: "Requiring adaptable staffing solutions that can change as project requirements evolve.",
|
||||
},*/
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-lgflex flex-col h-full"
|
||||
>
|
||||
<item.icon
|
||||
className="text-4xl mx-auto mb-4"
|
||||
// Using red to signify challenges/needs
|
||||
style={{ color: COLORS.primary }} // Use gold color
|
||||
/>
|
||||
<h4 className="text-xl font-semibold mb-2 font-poppins dark:text-white">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||
{item.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Our Solution: OMS Resource Augmentation */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6 font-poppins text-gray-900 dark:text-white leading-tight">
|
||||
Your Strategic Partner for IT Talent
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4">
|
||||
Owethu Managed Services provides high-caliber IT
|
||||
professionals through flexible engagement models tailored to
|
||||
your specific project requirements and business objectives. As a
|
||||
100% Black female-owned organization based in Centurion,
|
||||
Gauteng, we are committed to excellence and delivering value.
|
||||
</p>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 leading-relaxed font-semibold">
|
||||
Whether you need individual experts to augment your team or a
|
||||
fully managed service to deliver key project milestones, OMS
|
||||
offers the expertise and flexibility you need to succeed. We are
|
||||
also a Salesforce Partner, providing licenses, implementation,
|
||||
and managed services.
|
||||
</p>
|
||||
<Link
|
||||
href="/about" // Link to your main About Us page
|
||||
className="inline-flex items-center mt-4 text-gold-600 dark:text-gold-400 hover:text-gold-700 dark:hover:text-gold-300 font-semibold transition-colors duration-300"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
Learn More About OMS <FaArrowRight className="ml-2" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="relative h-64 md:h-80 rounded-lg overflow-hidden shadow-lg">
|
||||
{/* Placeholder Image - Replace with a relevant image (e.g., team collaboration, office) */}
|
||||
<Image
|
||||
src="/images/team-collaborative.png" // Replace with actual path
|
||||
alt="OMS Team Collaboration"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="bg-gray-200 dark:bg-gray-700"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Our Service Models Section */}
|
||||
<section
|
||||
id="service-models"
|
||||
className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700"
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Flexible Engagement Models
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Choose the approach that best suits your project needs, management
|
||||
style, and budget.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||
{serviceModels.map((model) => (
|
||||
<div
|
||||
key={model.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md flex flex-col"
|
||||
>
|
||||
<div className="flex items-center mb-4">
|
||||
<model.icon
|
||||
className="text-3xl mr-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h3 className="text-xl font-semibold font-poppins text-gray-900 dark:text-white">
|
||||
{model.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4 text-sm">
|
||||
{model.description}
|
||||
</p>
|
||||
<ul className="space-y-2 mb-4">
|
||||
{model.details.map((detail, index) => (
|
||||
<li key={index} className="flex items-start text-sm">
|
||||
<FaCheckCircle className="w-4 h-4 text-green-500 dark:text-green-400 mr-2 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-gray-700 dark:text-gray-300">
|
||||
{detail}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* Add the sentence as a separate paragraph for the Milestone model */}
|
||||
{model.title === "Milestone-Based Model" && (
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 mb-4 italic">
|
||||
OMS manages all deployed team members, deliverables, and
|
||||
velocity.
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm font-semibold text-gray-800 dark:text-gray-200 mt-auto pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
{model.pricingModel}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Capabilities & Expertise Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<FaTools
|
||||
className="text-5xl mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Our Core Capabilities
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
We provide skilled professionals across key IT disciplines at
|
||||
various experience levels (Junior, Intermediate, Senior).
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{capabilities.map((capability) => (
|
||||
<div
|
||||
key={capability.role}
|
||||
className="relative bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm border-t-4 dark:border-gold-400 transition-shadow hover:shadow-md"
|
||||
style={{ borderColor: COLORS.primary }}
|
||||
>
|
||||
<capability.icon
|
||||
className="text-3xl mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{capability.role}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed mb-3">
|
||||
{capability.description}
|
||||
</p>
|
||||
{/* Simplified skills display for the web page overview */}
|
||||
<h5 className="text-sm font-semibold mb-1 dark:text-gray-200">
|
||||
Key Areas of Expertise:
|
||||
</h5>
|
||||
<ul className="list-disc list-inside space-y-1 text-xs text-gray-600 dark:text-gray-300">
|
||||
{capability.skills[0].points.slice(0, 4).map(
|
||||
(
|
||||
point,
|
||||
index // Show first few points from senior/general list
|
||||
) => (
|
||||
<li key={index}>{point}</li>
|
||||
)
|
||||
)}
|
||||
{capability.skills[0].points.length > 4 && (
|
||||
<li>And more...</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-center text-md text-gray-700 dark:text-gray-300 mt-12 max-w-3xl mx-auto">
|
||||
Detailed capability breakdowns by seniority level (Junior,
|
||||
Intermediate, Senior) are available upon request.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 6. Benefits of Partnering with OMS Section */}
|
||||
{/* Benefits of Resource Augmentation
|
||||
<section className="py-16 md:py-24 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<FaRegHandshake
|
||||
className="text-5xl mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Why Choose OMS for Resource Augmentation?
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Leverage our expertise and flexible models to gain a competitive
|
||||
advantage.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{augmentationBenefits.map((benefit) => (
|
||||
<div
|
||||
key={benefit.title}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<benefit.icon
|
||||
className="text-4xl mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
{benefit.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
|
||||
{benefit.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
{/* 7. Proven Experience / Past Projects (High Level) */}
|
||||
<section className="py-16 md:py-24 bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-700">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-12 md:mb-16">
|
||||
<FaBusinessTime
|
||||
className="text-5xl mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-4">
|
||||
Proven Track Record Across Industries
|
||||
</h2>
|
||||
<p className="text-md md:text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
We have successfully delivered resources and managed projects in
|
||||
complex environments, including:
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto text-center">
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm">
|
||||
<FaShieldAlt
|
||||
className="text-3xl mx-auto mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
Financial Services & Compliance
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Expertise in FIC/KYC remediation, including data architecture
|
||||
and management information design, as well as core banking
|
||||
systems and end-to-end optimisation of lending and credit
|
||||
processes.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm">
|
||||
<FaUsersCog
|
||||
className="text-3xl mx-auto mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
Customer Management & Onboarding
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Crafting seamless, data-driven onboarding experiences through
|
||||
straight-through processing, enhanced customer journeys,<br/>
|
||||
real-time insights, and a 360-degree customer view.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow-sm">
|
||||
<FaSitemap
|
||||
className="text-3xl mx-auto mb-3"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h4 className="text-lg font-semibold mb-2 font-poppins dark:text-white">
|
||||
Platform & Process Automation
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Specialising in Straight-Through Processing (STP) for banking
|
||||
platforms, seamless API integration, and end-to-end Salesforce
|
||||
implementations to drive efficiency and reduce manual
|
||||
intervention.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Optional: Mention specific technologies briefly if desired */}
|
||||
{/* <p className="text-center text-md text-gray-500 dark:text-gray-400 mt-8 max-w-4xl mx-auto italic">
|
||||
Expertise includes: Salesforce, AWS, Azure, Data Warehousing, BI Tools, BPMN, Various Banking Systems (Sybrin, Flexicube, etc.), Automation Frameworks (Selenium), and more.
|
||||
</p> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 8. Implementation Approaches / Methodologies */}
|
||||
<section className="py-16 md:py-24 bg-gray-800 text-white">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaLayerGroup
|
||||
className="text-5xl mx-auto mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins mb-4">
|
||||
Adaptable Delivery Methodologies
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-300 mb-8">
|
||||
We are proficient in various project management frameworks and adapt
|
||||
our approach to align with your organizational standards and project
|
||||
needs.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center items-center gap-8">
|
||||
{methodologies.map((method) => (
|
||||
<div
|
||||
key={method.name}
|
||||
className="flex flex-col items-center p-4 bg-gray-700 rounded-lg w-48"
|
||||
>
|
||||
<method.icon
|
||||
className="text-4xl mb-2"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<span className="font-semibold text-gray-200">
|
||||
{method.name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 9. Clients & Partners Section */}
|
||||
<section className="py-16 md:py-24 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-center text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-white mb-12">
|
||||
Trusted by Leading Organizations
|
||||
</h2>
|
||||
{/* Updated the grid classes here */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-8 md:gap-12 items-center max-w-5xl mx-auto">
|
||||
{clientLogos.map((logo) => (
|
||||
<div
|
||||
key={logo.alt}
|
||||
className="relative h-16 md:h-20 filter grayscale hover:grayscale-0 transition duration-300 brightness-75 hover:brightness-100 dark:brightness-150 dark:hover:brightness-100 dark:invert dark:hover:invert-0"
|
||||
>
|
||||
<Image
|
||||
src={logo.src}
|
||||
alt={logo.alt}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 10. Call to Action (CTA) Section */}
|
||||
<section
|
||||
className="py-16 md:py-24 bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-gray-900"
|
||||
style={{ backgroundColor: COLORS.primary }}
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins">
|
||||
Ready to Enhance Your IT Team?
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed mb-8 font-poppins text-gray-800">
|
||||
Let's discuss how OMS resource augmentation or managed services
|
||||
can help you achieve your project goals. Contact us today for a
|
||||
consultation tailored to your specific needs.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<Link
|
||||
href="/contact?subject=Resource Augmentation Consultation"
|
||||
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300"
|
||||
>
|
||||
Request a Consultation
|
||||
</Link>
|
||||
<Link
|
||||
href="mailto:admin@oms.africa" // Direct email link
|
||||
className="inline-block bg-transparent border-2 border-gray-800 text-gray-800 font-bold py-3 px-8 rounded-md hover:bg-gray-800 hover:text-white transition-colors duration-300"
|
||||
>
|
||||
Email Us Directly
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
298
app/(website)/tech-talk/[slug]/page.tsx
Normal file
@ -0,0 +1,298 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import { getPostBySlug, getPosts } from "@/lib/query/post";
|
||||
import type { Metadata } from "next";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
ContentBlock,
|
||||
OutputData,
|
||||
ParagraphData,
|
||||
HeaderData,
|
||||
ListData,
|
||||
ImageData,
|
||||
QuoteData,
|
||||
CodeData,
|
||||
} from "@/types";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const posts = await getPosts();
|
||||
if (!Array.isArray(posts)) {
|
||||
console.error("getPosts did not return an array:", posts);
|
||||
return [];
|
||||
}
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
function extractDescriptionFromBlocks(
|
||||
content: OutputData | null
|
||||
): string | null {
|
||||
if (
|
||||
!content ||
|
||||
!content.blocks ||
|
||||
!Array.isArray(content.blocks) ||
|
||||
content.blocks.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const firstParagraph = content.blocks.find(
|
||||
(block): block is ContentBlock & { data: ParagraphData } =>
|
||||
block.type === "paragraph"
|
||||
);
|
||||
if (firstParagraph?.data?.text) {
|
||||
const plainText = firstParagraph.data.text
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/&[^;]+;/g, "");
|
||||
return plainText.substring(0, 160) + (plainText.length > 160 ? "..." : "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const post = await getPostBySlug(slug, {
|
||||
fields: [
|
||||
"slug",
|
||||
"status",
|
||||
"user_created",
|
||||
"date_created",
|
||||
"user_updated",
|
||||
"date_updated",
|
||||
"title",
|
||||
"content",
|
||||
"excerpt",
|
||||
"featured_image",
|
||||
],
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
title: "Post Not Found",
|
||||
};
|
||||
}
|
||||
|
||||
const imageUrl = post.featured_image;
|
||||
|
||||
const descriptionFromContent =
|
||||
post.content &&
|
||||
typeof post.content === "object" &&
|
||||
Array.isArray(post.content.blocks)
|
||||
? extractDescriptionFromBlocks(post.content as OutputData)
|
||||
: null;
|
||||
|
||||
const description =
|
||||
post.excerpt || descriptionFromContent || "Read this OMS TechTalk article.";
|
||||
|
||||
const publishedTime = post.date_created
|
||||
? new Date(post.date_created).toISOString()
|
||||
: "";
|
||||
|
||||
return {
|
||||
title: `${post.title} | OMS TechTalk`,
|
||||
description: description,
|
||||
alternates: {
|
||||
canonical: `/tech-talk/${post.slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: description,
|
||||
url: `https://oms.africa/tech-talk/${post.slug}`,
|
||||
type: "article",
|
||||
publishedTime: publishedTime,
|
||||
...(imageUrl && {
|
||||
images: [
|
||||
{
|
||||
url: imageUrl,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: post.title,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: post.title,
|
||||
description: description,
|
||||
...(imageUrl && { images: [imageUrl] }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const renderBlock = (block: ContentBlock) => {
|
||||
const renderListItems = (items: string[]) => {
|
||||
return items.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-2"
|
||||
dangerouslySetInnerHTML={{ __html: item }}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
switch (block.type) {
|
||||
case "header":
|
||||
const headerBlock = block as ContentBlock & { data: HeaderData };
|
||||
const level = headerBlock.data.level || 2;
|
||||
// Ensure HeaderTag has a type that JSX understands for intrinsic elements.
|
||||
const HeaderTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
return (
|
||||
<HeaderTag
|
||||
key={block.id}
|
||||
className={`text-${
|
||||
6 - (headerBlock.data.level || 2)
|
||||
}xl font-bold my-4 text-primary`}
|
||||
dangerouslySetInnerHTML={{ __html: headerBlock.data.text }}
|
||||
/>
|
||||
);
|
||||
case "paragraph":
|
||||
const paragraphBlock = block as ContentBlock & { data: ParagraphData };
|
||||
return (
|
||||
<p
|
||||
key={block.id}
|
||||
className="my-4 leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraphBlock.data.text }}
|
||||
/>
|
||||
);
|
||||
case "list":
|
||||
const listBlock = block as ContentBlock & { data: ListData };
|
||||
const ListTag = listBlock.data.style === "ordered" ? "ol" : "ul";
|
||||
return (
|
||||
<ListTag
|
||||
key={block.id}
|
||||
className={`my-4 pl-6 ${
|
||||
listBlock.data.style === "ordered" ? "list-decimal" : "list-disc"
|
||||
}`}
|
||||
>
|
||||
{listBlock.data.items &&
|
||||
Array.isArray(listBlock.data.items) &&
|
||||
renderListItems(listBlock.data.items)}
|
||||
</ListTag>
|
||||
);
|
||||
case "image":
|
||||
const imageBlock = block as ContentBlock & { data: ImageData };
|
||||
const imageUrl = imageBlock.data.file?.url;
|
||||
|
||||
if (!imageUrl) return null;
|
||||
|
||||
return (
|
||||
<div key={block.id} className="my-6">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={imageBlock.data.caption || "Blog post image"}
|
||||
width={imageBlock.data.file?.width || 800}
|
||||
height={imageBlock.data.file?.height || 450}
|
||||
className="rounded-md shadow-sm mx-auto"
|
||||
/>
|
||||
{imageBlock.data.caption && (
|
||||
<p className="text-center text-sm text-muted-foreground mt-2">
|
||||
{imageBlock.data.caption}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "quote":
|
||||
const quoteBlock = block as ContentBlock & { data: QuoteData };
|
||||
return (
|
||||
<blockquote
|
||||
key={block.id}
|
||||
className="my-6 pl-4 border-l-4 border-primary italic text-muted-foreground"
|
||||
>
|
||||
<p dangerouslySetInnerHTML={{ __html: quoteBlock.data.text }} />
|
||||
{quoteBlock.data.caption && (
|
||||
<footer className="text-sm mt-2">
|
||||
- {quoteBlock.data.caption}
|
||||
</footer>
|
||||
)}
|
||||
</blockquote>
|
||||
);
|
||||
case "code":
|
||||
const codeBlock = block as ContentBlock & { data: CodeData };
|
||||
const codeContent = codeBlock.data.code || "";
|
||||
return (
|
||||
<pre
|
||||
key={block.id}
|
||||
className="my-6 p-4 bg-muted text-muted-foreground rounded-md overflow-x-auto text-sm"
|
||||
>
|
||||
<code>{codeContent}</code>
|
||||
</pre>
|
||||
);
|
||||
default:
|
||||
console.warn(`Unknown block type: ${block.type}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default async function PostPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const post = await getPostBySlug(slug);
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const contentBlocks =
|
||||
post.content &&
|
||||
typeof post.content === "object" &&
|
||||
Array.isArray(post.content.blocks)
|
||||
? (post.content.blocks as ContentBlock[])
|
||||
: null;
|
||||
|
||||
const coverImageUrl =
|
||||
post.featured_image + "?width=1200&height=600&quality=80&fit=cover";
|
||||
|
||||
return (
|
||||
<article className="container mx-auto px-4 py-8">
|
||||
<header className="text-center mb-12 max-w-3xl mx-auto">
|
||||
{coverImageUrl && (
|
||||
<div className="relative w-full h-64 md:h-96 mb-6 rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={coverImageUrl}
|
||||
alt={`Cover image for ${post.title}`}
|
||||
fill={true}
|
||||
style={{ objectFit: "cover" }}
|
||||
className="shadow-md"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold leading-tight mb-4 text-primary">
|
||||
{post.title}
|
||||
</h1>
|
||||
{post.date_created && (
|
||||
<p className="text-muted-foreground text-sm mb-2">
|
||||
Published on {format(new Date(post.date_created), "MMMM dd, yyyy")}
|
||||
</p>
|
||||
)}
|
||||
{post.excerpt && (
|
||||
<p className="text-lg text-muted-foreground italic mt-4">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="container mx-auto px-4 max-w-3xl">
|
||||
<div
|
||||
className="prose prose-lg lg:prose-xl dark:prose-invert max-w-none mx-auto
|
||||
prose-headings:text-primary prose-a:text-blue-600 hover:prose-a:text-blue-800 dark:prose-a:text-blue-400 dark:hover:prose-a:text-blue-300
|
||||
prose-strong:font-semibold prose-img:rounded-md prose-img:shadow-sm"
|
||||
>
|
||||
{contentBlocks && contentBlocks.length > 0 ? (
|
||||
contentBlocks.map((block) => renderBlock(block))
|
||||
) : (
|
||||
<p>Content is not available or in an unexpected format.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -1,39 +1,8 @@
|
||||
import React from "react";
|
||||
import BlogPostCard from "@/components/BlogPostCard";
|
||||
import { prisma } from "@/lib/prisma"; // Import Prisma client
|
||||
import { auth } from "@/auth"; // Import auth to check session
|
||||
import Button from "@/components/ui/Button"; // Import Button component
|
||||
import type { Metadata } from "next";
|
||||
|
||||
interface Post {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
content: string;
|
||||
excerpt?: string | null;
|
||||
imageUrl?: string | null;
|
||||
published: boolean;
|
||||
authorId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
// --- Fetch Posts ---
|
||||
async function getPublishedPosts() {
|
||||
try {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: { published: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
// select needed fields if not all
|
||||
});
|
||||
return posts;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch posts:", error);
|
||||
return []; // Return empty array on error
|
||||
}
|
||||
}
|
||||
|
||||
import { getPosts } from "@/lib/query/post";
|
||||
import { Post } from "@/types";
|
||||
// --- SEO Metadata ---
|
||||
export const metadata: Metadata = {
|
||||
title: "OMS TechTalk | Insights & Innovation",
|
||||
@ -42,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.",
|
||||
@ -65,8 +35,7 @@ export const metadata: Metadata = {
|
||||
|
||||
// --- Page Component ---
|
||||
const TechTalkPage = async () => {
|
||||
const posts = await getPublishedPosts();
|
||||
const session = await auth(); // Get session info
|
||||
const posts: Post[] = await getPosts();
|
||||
|
||||
return (
|
||||
<div className="bg-background text-foreground">
|
||||
@ -80,28 +49,21 @@ const TechTalkPage = async () => {
|
||||
Insights, trends, and discussions on the latest in technology,
|
||||
innovation, and digital transformation from the experts at OMS.
|
||||
</p>
|
||||
{/* Conditionally render Create Post button */}
|
||||
{session?.user && (
|
||||
<div className="mt-8">
|
||||
<Button href="/tech-talk/create" variant="primary">
|
||||
Create New Post
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Blog Post Grid */}
|
||||
{posts.length > 0 ? (
|
||||
{false && posts.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10">
|
||||
{posts.map((post: Post) => (
|
||||
<BlogPostCard
|
||||
key={post.id} // Use post ID as key
|
||||
key={post.slug}
|
||||
slug={post.slug}
|
||||
title={post.title}
|
||||
excerpt={post.excerpt ?? post.content.substring(0, 150) + "..."}
|
||||
// Use imageUrl from DB or a default placeholder
|
||||
imageUrl={post.imageUrl ?? "/posts/default-placeholder.jpg"} // Provide a default image
|
||||
author={"OMS Team"} // Replace with actual author logic if available (e.g., post.user.name)
|
||||
date={new Date(post.createdAt).toLocaleDateString("en-US", {
|
||||
excerpt={post.excerpt ?? "No excerpt available"}
|
||||
imageUrl={
|
||||
post.featured_image ?? "/posts/default-placeholder.jpg"
|
||||
}
|
||||
author={"OMS Team"}
|
||||
date={new Date(post.date_created).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
|
||||
54
app/(website)/vacancies/[slug]/page.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { Vacancy } from "@/types";
|
||||
import VacancyClientContent from "../_components/VacancyClientContent";
|
||||
|
||||
interface ExtendedVacancy extends Vacancy {
|
||||
company?: {
|
||||
name: string;
|
||||
logoUrl?: string;
|
||||
websiteUrl?: string;
|
||||
};
|
||||
skills?: string[];
|
||||
}
|
||||
|
||||
async function getVacancy(slug: string): Promise<ExtendedVacancy | null> {
|
||||
const res = await fetch(`${process.env.WEBSITE_URL}/api/vacancies/${slug}`, {
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) return null;
|
||||
console.error(`Failed to fetch vacancy ${slug}: ${res.statusText}`);
|
||||
throw new Error("Failed to fetch vacancy details");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export default async function VacancyDetailsPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const vacancy = await getVacancy(slug);
|
||||
|
||||
if (!vacancy) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const shareUrl = `${process.env.WEBSITE_URL}/vacancies/${slug}`;
|
||||
const shareTitle = encodeURIComponent(
|
||||
`Job Opening: ${vacancy.title} at ${
|
||||
vacancy.company?.name || "Owethu Managed Services"
|
||||
}`
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-black text-gray-800 font-poppins overflow-x-hidden min-h-screen py-12 md:py-5">
|
||||
<VacancyClientContent
|
||||
vacancy={vacancy}
|
||||
shareUrl={shareUrl}
|
||||
shareTitle={shareTitle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
app/(website)/vacancies/_components/Badge.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
export const Badge = ({
|
||||
children,
|
||||
icon: Icon,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
icon?: React.ElementType;
|
||||
}) => (
|
||||
<span className="inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-200 font-poppins">
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
31
app/(website)/vacancies/_components/ListSection.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { COLORS } from "@/constants";
|
||||
|
||||
export const ListSection = ({
|
||||
title,
|
||||
items,
|
||||
icon: Icon,
|
||||
}: {
|
||||
title: string;
|
||||
items?: string[];
|
||||
icon?: React.ElementType;
|
||||
}) => {
|
||||
if (!items || items.length === 0) return null;
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white font-poppins border-b border-gray-200 pb-2">
|
||||
{Icon && (
|
||||
<Icon
|
||||
className="h-5 w-5 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
)}
|
||||
{title}
|
||||
</h3>
|
||||
<ul className="list-disc space-y-2 pl-6 text-gray-700 dark:text-white font-poppins text-sm leading-relaxed">
|
||||
{items.map((item, index) => (
|
||||
<li key={index}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
22
app/(website)/vacancies/_components/MetadataItem.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { COLORS } from "@/constants";
|
||||
|
||||
export const MetadataItem = ({
|
||||
icon: Icon,
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-start space-x-2 text-sm text-gray-700 dark:text-white font-poppins">
|
||||
<Icon
|
||||
className="h-5 w-5 mt-0.5 flex-shrink-0 text-primary"
|
||||
aria-hidden="true"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
<div>
|
||||
<span className="font-semibold">{label}:</span> {value}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
349
app/(website)/vacancies/_components/VacancyClientContent.tsx
Normal file
@ -0,0 +1,349 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useState /* useRef, useEffect - Remove these if no longer needed */,
|
||||
} from "react"; // Updated imports
|
||||
import Link from "next/link";
|
||||
import { Vacancy } from "@/types";
|
||||
import VacancyApplicationForm from "@/components/VacancyApplicationForm";
|
||||
|
||||
import {
|
||||
FaMapMarkerAlt,
|
||||
FaBriefcase,
|
||||
FaClock,
|
||||
FaCalendarAlt,
|
||||
FaDollarSign,
|
||||
FaGraduationCap,
|
||||
FaShareAlt,
|
||||
FaCheckCircle,
|
||||
FaBuilding,
|
||||
FaLink,
|
||||
FaListUl,
|
||||
FaInfoCircle,
|
||||
FaStar,
|
||||
FaGift,
|
||||
FaTools,
|
||||
} from "react-icons/fa";
|
||||
import { formatDate } from "@/lib/helpers";
|
||||
import { COLORS } from "@/constants";
|
||||
import Image from "next/image";
|
||||
import { MetadataItem } from "./MetadataItem";
|
||||
import { Badge } from "./Badge";
|
||||
import { ListSection } from "./ListSection";
|
||||
import Button from "@/components/ui/Button"; // Assuming you have a Button component
|
||||
import Modal from "@/components/ui/Modal";
|
||||
|
||||
interface VacancyClientContentProps {
|
||||
vacancy: Vacancy & {
|
||||
company?: { name: string; logoUrl?: string; websiteUrl?: string };
|
||||
skills?: string[];
|
||||
};
|
||||
shareUrl: string;
|
||||
shareTitle: string;
|
||||
}
|
||||
|
||||
export default function VacancyClientContent({
|
||||
vacancy,
|
||||
shareUrl,
|
||||
shareTitle,
|
||||
}: VacancyClientContentProps) {
|
||||
// State to control modal visibility
|
||||
const [isApplyModalOpen, setIsApplyModalOpen] = useState(false);
|
||||
// const applyFormRef = useRef<HTMLDivElement>(null); // Remove this ref
|
||||
// Remove the useEffect for scrolling
|
||||
|
||||
const handleOpenApplyModal = () => {
|
||||
setIsApplyModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseApplyModal = () => {
|
||||
setIsApplyModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{" "}
|
||||
{/* Adjusted padding */}
|
||||
{/* --- Company Header --- */}
|
||||
{vacancy.company && (
|
||||
<div className="mb-10 flex flex-col sm:flex-row items-center justify-between gap-6 p-6 bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center gap-4 flex-grow min-w-0">
|
||||
{" "}
|
||||
{/* Added flex-grow and min-w-0 for wrapping */}
|
||||
{vacancy.company.logoUrl ? (
|
||||
<Image
|
||||
src={vacancy.company.logoUrl}
|
||||
alt={`${vacancy.company.name} Logo`}
|
||||
width={64} // Add width/height for better layout shift prevention
|
||||
height={64}
|
||||
className="h-16 w-16 object-contain rounded-md flex-shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-16 w-16 bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center flex-shrink-0">
|
||||
<FaBuilding
|
||||
className="h-8 w-8 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
{" "}
|
||||
{/* Added min-w-0 here too */}
|
||||
<h1 className="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white truncate">
|
||||
{" "}
|
||||
{/* Consider truncate */}
|
||||
{vacancy.title}
|
||||
</h1>
|
||||
<p className="text-lg text-gray-700 dark:text-gray-300">
|
||||
at {vacancy.company.name}
|
||||
</p>
|
||||
{vacancy.company.websiteUrl && (
|
||||
<Link
|
||||
href={vacancy.company.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 inline-flex items-center gap-1 group mt-1"
|
||||
>
|
||||
Visit website{" "}
|
||||
<FaLink className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-1" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Apply Button in Header */}
|
||||
<Button
|
||||
variant="primary" // Use your Button component if available
|
||||
onClick={handleOpenApplyModal}
|
||||
className="flex-shrink-0 whitespace-nowrap" // Prevent shrinking/wrapping
|
||||
>
|
||||
Apply Now
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{/* --- End Company Header --- */}
|
||||
{/* --- Main Grid Layout --- */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 lg:gap-10">
|
||||
{/* --- Main Content Area (Left Column) --- */}
|
||||
<div className="lg:col-span-2 bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6 md:p-8 border border-gray-200 dark:border-gray-700">
|
||||
{/* Title if no company header */}
|
||||
{!vacancy.company && (
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white">
|
||||
{vacancy.title}
|
||||
</h1>
|
||||
{/* Apply Button if no header */}
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleOpenApplyModal}
|
||||
className="ml-4 flex-shrink-0 whitespace-nowrap"
|
||||
>
|
||||
Apply Now
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Job Description */}
|
||||
<div className="mb-8">
|
||||
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
||||
<FaInfoCircle
|
||||
className="h-5 w-5 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Job Description
|
||||
</h3>
|
||||
<div
|
||||
className="prose prose-sm sm:prose-base max-w-none text-gray-700 dark:text-gray-300 font-poppins leading-relaxed prose-headings:text-gray-900 prose-headings:dark:text-white prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-strong:text-gray-800 dark:prose-strong:text-gray-200"
|
||||
dangerouslySetInnerHTML={{ __html: vacancy.description || "" }} // Use dangerouslySetInnerHTML if description is HTML
|
||||
>
|
||||
{/* Or render as text if plain: <p>{vacancy.description}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- List Sections (Responsibilities, Qualifications, Skills, Benefits) --- */}
|
||||
{/* Assuming ListSection renders conditionally if items are empty */}
|
||||
<ListSection
|
||||
title="Responsibilities"
|
||||
items={vacancy.responsibilities}
|
||||
icon={FaListUl}
|
||||
/>
|
||||
<ListSection
|
||||
title="Required Qualifications"
|
||||
items={vacancy.requiredQualifications}
|
||||
icon={FaStar}
|
||||
/>
|
||||
<ListSection
|
||||
title="Preferred Qualifications"
|
||||
items={vacancy.preferredQualifications}
|
||||
icon={FaStar}
|
||||
/>
|
||||
|
||||
{/* Skills Section */}
|
||||
{vacancy.skills && vacancy.skills.length > 0 && (
|
||||
<div className="mb-8">
|
||||
<h3 className="flex items-center gap-2 text-xl font-semibold mb-4 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
||||
<FaTools
|
||||
className="h-5 w-5 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Skills
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{vacancy.skills.map((skill, index) => (
|
||||
<Badge key={index}>{skill}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Benefits Section */}
|
||||
{vacancy.benefits && vacancy.benefits.length > 0 && (
|
||||
<div className="mb-8">
|
||||
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
||||
<FaGift
|
||||
className="h-5 w-5 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Benefits
|
||||
</h3>
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-2 text-gray-700 dark:text-gray-300 mt-3 text-sm">
|
||||
{vacancy.benefits.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center space-x-2 font-poppins"
|
||||
>
|
||||
<FaCheckCircle className="h-4 w-4 text-green-600 flex-shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{/* --- End List Sections --- */}
|
||||
|
||||
{/* Apply button below main content (redundant if header button exists, keep or remove as needed) */}
|
||||
<div className="mt-10 text-center lg:text-left">
|
||||
<Button variant="primary" onClick={handleOpenApplyModal}>
|
||||
Apply for this Position
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* --- End Main Content Area --- */}
|
||||
|
||||
{/* --- Sidebar (Right Column) --- */}
|
||||
<div className="lg:col-span-1 space-y-8">
|
||||
{/* Metadata Card */}
|
||||
<div className="bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg p-6 border-l-4 border-primary dark:border-gold-500">
|
||||
<h3 className="text-xl font-semibold mb-5 text-gray-900 dark:text-white font-poppins">
|
||||
Job Overview
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<MetadataItem
|
||||
icon={FaBuilding}
|
||||
label="Department"
|
||||
value={vacancy.department}
|
||||
/>
|
||||
<MetadataItem
|
||||
icon={FaMapMarkerAlt}
|
||||
label="Location"
|
||||
value={
|
||||
<>
|
||||
{vacancy.location.city}, {vacancy.location.country}{" "}
|
||||
{vacancy.location.remote && <Badge>Remote Possible</Badge>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<MetadataItem
|
||||
icon={FaBriefcase}
|
||||
label="Type"
|
||||
value={<Badge>{vacancy.employmentType}</Badge>}
|
||||
/>
|
||||
<MetadataItem
|
||||
icon={FaGraduationCap}
|
||||
label="Level"
|
||||
value={vacancy.experienceLevel}
|
||||
/>
|
||||
{vacancy.salary && (
|
||||
<MetadataItem
|
||||
icon={FaDollarSign}
|
||||
label="Salary"
|
||||
value={
|
||||
<span className="font-semibold text-gray-800 dark:text-gray-200">
|
||||
{vacancy.salary.min.toLocaleString()} -{" "}
|
||||
{vacancy.salary.max.toLocaleString()}{" "}
|
||||
{vacancy.salary.currency} {vacancy.salary.period}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<MetadataItem
|
||||
icon={FaCalendarAlt}
|
||||
label="Posted"
|
||||
value={formatDate(vacancy.postedDate)}
|
||||
/>
|
||||
{vacancy.applicationDeadline && (
|
||||
<MetadataItem
|
||||
icon={FaClock}
|
||||
label="Apply by"
|
||||
value={formatDate(vacancy.applicationDeadline)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Share Card */}
|
||||
<div className="bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg p-6 border-l-4 border-primary dark:border-gold-500">
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2 font-poppins">
|
||||
<FaShareAlt
|
||||
className="h-5 w-5 text-primary"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Share this opening
|
||||
</h3>
|
||||
<div className="flex space-x-3 text-sm font-poppins">
|
||||
<a
|
||||
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
|
||||
shareUrl
|
||||
)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
<a
|
||||
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(
|
||||
shareUrl
|
||||
)}&text=${shareTitle}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
<a
|
||||
href={`mailto:?subject=${shareTitle}&body=Check out this job opening: ${encodeURIComponent(
|
||||
shareUrl
|
||||
)}`}
|
||||
className="text-gray-600 hover:underline dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isApplyModalOpen}
|
||||
onClose={handleCloseApplyModal}
|
||||
title={`Apply for: ${vacancy.title}`}
|
||||
size="4xl"
|
||||
>
|
||||
<VacancyApplicationForm
|
||||
vacancyId={vacancy.id}
|
||||
vacancyTitle={vacancy.title}
|
||||
onClose={handleCloseApplyModal}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
236
app/(website)/vacancies/page.tsx
Normal file
@ -0,0 +1,236 @@
|
||||
"use client"; // <-- Add this line to use state
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react"; // <-- Import useState
|
||||
import {
|
||||
FaMapMarkerAlt,
|
||||
FaBriefcase,
|
||||
FaPaperPlane,
|
||||
FaArrowRight,
|
||||
FaRegClock,
|
||||
FaSearch,
|
||||
} from "react-icons/fa";
|
||||
import { demoVacancies } from "@/lib/demo-data/vacancies";
|
||||
import { Vacancy } from "@/types";
|
||||
import Button from "@/components/ui/Button";
|
||||
import Modal from "@/components/ui/Modal"; // <-- Import your Modal component
|
||||
import { COLORS } from "@/constants";
|
||||
import VacancyApplicationForm from "@/components/VacancyApplicationForm";
|
||||
|
||||
// Metadata object might need adjustment depending on your setup with client components
|
||||
// If using App Router, keep it, Next.js handles it.
|
||||
/*
|
||||
export const metadata: Metadata = {
|
||||
title: "Current Vacancies | OMS",
|
||||
description:
|
||||
"Explore exciting career opportunities at OMS. Find your perfect role or submit your CV for future consideration.",
|
||||
};
|
||||
*/
|
||||
|
||||
// --- VacancyCard Component (no changes needed here) ---
|
||||
interface VacancyCardProps {
|
||||
vacancy: Vacancy;
|
||||
}
|
||||
|
||||
function VacancyCard({ vacancy }: VacancyCardProps) {
|
||||
const formatDate = (dateString: string | undefined) => {
|
||||
if (!dateString) return "Date N/A";
|
||||
try {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
} catch {
|
||||
return "Invalid Date";
|
||||
}
|
||||
};
|
||||
const postedDate = formatDate(vacancy.postedDate);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/vacancies/${vacancy.slug}`}
|
||||
className="group block w-full transform transition duration-300 ease-in-out hover:-translate-y-1" // Add subtle lift on hover
|
||||
>
|
||||
<div
|
||||
className="relative flex flex-col h-full overflow-hidden rounded-lg bg-white dark:bg-gray-800 p-6 shadow-md transition-shadow duration-300 hover:shadow-xl border-l-4 dark:border-l-yellow-500" // Card base style + left border + dark mode
|
||||
style={{ borderColor: COLORS.primary }} // Apply gold border color (consider dark mode alternative if needed)
|
||||
>
|
||||
<div className="flex-grow">
|
||||
<h3 className="mb-2 text-xl font-bold font-poppins text-gray-900 dark:text-gray-100 transition-colors group-hover:text-gray-700 dark:group-hover:text-gray-300">
|
||||
{vacancy.title}
|
||||
</h3>
|
||||
<div className="mb-4 flex flex-col space-y-2 text-sm text-gray-600 dark:text-gray-400 font-poppins">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<FaMapMarkerAlt
|
||||
className="h-4 w-4 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
{vacancy.location.city}{" "}
|
||||
{vacancy.location.remote && (
|
||||
<span className="ml-1 rounded bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
Remote
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<FaBriefcase
|
||||
className="h-4 w-4 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{vacancy.employmentType}
|
||||
</span>
|
||||
{vacancy.postedDate && (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<FaRegClock
|
||||
className="h-4 w-4 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Posted: {postedDate}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-auto pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<span
|
||||
className="inline-flex items-center text-sm font-medium font-poppins"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
>
|
||||
View Details
|
||||
<FaArrowRight
|
||||
className="ml-1 h-4 w-4 transition-transform duration-300 group-hover:translate-x-1"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
// --- End Vacancy Card Component ---
|
||||
|
||||
// --- Vacancies Page ---
|
||||
export default function VacanciesPage() {
|
||||
// TODO: Replace demoVacancies with actual API call if needed client-side,
|
||||
// or fetch server-side and pass as props if using Pages Router.
|
||||
// For App Router, `async function` fetches server-side by default.
|
||||
const vacancies = demoVacancies;
|
||||
|
||||
// --- State for the "Future Positions" Modal ---
|
||||
const [isFuturePositionModalOpen, setIsFuturePositionModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const handleOpenFuturePositionModal = () =>
|
||||
setIsFuturePositionModalOpen(true);
|
||||
const handleCloseFuturePositionModal = () =>
|
||||
setIsFuturePositionModalOpen(false);
|
||||
// --- End State ---
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-200 overflow-x-hidden font-poppins">
|
||||
{/* Section 1: Hero / Page Header */}
|
||||
<section className="relative bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 text-white py-20 md:py-28">
|
||||
<div className="absolute inset-0 bg-black opacity-30 dark:opacity-50"></div>
|
||||
<div className="container mx-auto px-6 text-center relative z-10">
|
||||
<h1
|
||||
className="text-4xl md:text-5xl lg:text-6xl font-bold mb-4 font-poppins drop-shadow-md"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
>
|
||||
Career Opportunities
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed text-gray-200 dark:text-gray-300 font-poppins">
|
||||
Join our team of innovators and experts. Explore current openings at
|
||||
OMS or submit your CV for future consideration.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 2: Vacancy List */}
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-gray-900 dark:text-gray-100 mb-12 text-center">
|
||||
Current Openings
|
||||
</h2>
|
||||
{vacancies.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{vacancies.map((vacancy) => (
|
||||
<VacancyCard key={vacancy.id} vacancy={vacancy} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-10 max-w-2xl mx-auto rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-10 text-center shadow-sm">
|
||||
<FaSearch
|
||||
className="mx-auto mb-5 h-12 w-12"
|
||||
style={{ color: COLORS.primary }} // Keep gold or use dark:text-yellow-400
|
||||
/>
|
||||
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100 font-poppins mb-2">
|
||||
No Open Vacancies Right Now
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 font-poppins">
|
||||
We're not actively hiring for specific roles at the moment,
|
||||
but we're always looking for passionate and talented
|
||||
individuals. Check back soon or submit your CV below!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 3: Future Positions / CV Submission */}
|
||||
<section
|
||||
className="py-16 md:py-24 text-gray-900 dark:text-gray-800" // Adjust text color for dark mode contrast on gold bg
|
||||
style={{ backgroundColor: COLORS.primary }} // Keep gold background
|
||||
>
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<FaPaperPlane className="text-5xl mx-auto mb-5 text-gray-800 dark:text-gray-900" />{" "}
|
||||
{/* Ensure icon contrast */}
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 font-poppins text-gray-900 dark:text-gray-900">
|
||||
{" "}
|
||||
{/* Ensure heading contrast */}
|
||||
Don't See the Right Fit?
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl max-w-3xl mx-auto leading-relaxed font-poppins text-gray-800 dark:text-gray-800">
|
||||
{" "}
|
||||
{/* Ensure text contrast */}
|
||||
We're always looking for talented individuals to join our
|
||||
journey. Submit your CV, and we'll keep you in mind for future
|
||||
openings that match your profile.
|
||||
</p>
|
||||
<div className="mt-10">
|
||||
{/* --- Updated Button --- */}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleOpenFuturePositionModal}
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="inline-flex items-center gap-2 group font-poppins bg-gray-800 text-black hover:bg-gray-900 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200" // Adjusted dark mode button colors
|
||||
>
|
||||
Submit Your CV
|
||||
<FaArrowRight className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-1" />
|
||||
</Button>
|
||||
{/* --- End Updated Button --- */}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- Modal for Future Position Application --- */}
|
||||
<Modal
|
||||
isOpen={isFuturePositionModalOpen}
|
||||
onClose={handleCloseFuturePositionModal}
|
||||
title="Apply for Future Positions"
|
||||
size="4xl"
|
||||
>
|
||||
<VacancyApplicationForm
|
||||
vacancyId="future-position"
|
||||
vacancyTitle="General Application / Future Position"
|
||||
onClose={handleCloseFuturePositionModal}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
158
app/achievements/_data/achievementsDefaults.ts
Normal file
@ -0,0 +1,158 @@
|
||||
// /app/achievements/_data/achievementsDefaults.ts (or similar)
|
||||
|
||||
export interface Award {
|
||||
id: string | number;
|
||||
name: string;
|
||||
year: number | string;
|
||||
awardingBody: string;
|
||||
description?: string;
|
||||
imageUrl?: string; // URL for an award logo or image
|
||||
}
|
||||
|
||||
export interface Certification {
|
||||
id: string | number;
|
||||
name: string;
|
||||
issuingBody: string;
|
||||
logoUrl?: string;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
export interface Partner {
|
||||
id: string | number;
|
||||
name: string;
|
||||
logoUrl: string; // Partner logo is usually essential
|
||||
websiteUrl?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface GalleryImage {
|
||||
id: string | number;
|
||||
imageUrl: string;
|
||||
caption?: string;
|
||||
alt: string;
|
||||
event?: string; // Optional: Name of the event
|
||||
date?: string; // Optional: Date of the event/photo
|
||||
}
|
||||
|
||||
export interface Milestone {
|
||||
id: string | number;
|
||||
description: string;
|
||||
date: string; // e.g., "Q4 2023", "June 2022"
|
||||
}
|
||||
|
||||
// --- Default Placeholder Data ---
|
||||
|
||||
export const defaultAwards: Award[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Top BEE Level 1 Contributor",
|
||||
year: 2023,
|
||||
awardingBody: "SANAS Accreditation Body",
|
||||
description:
|
||||
"Recognized for commitment to Broad-Based Black Economic Empowerment.",
|
||||
imageUrl: "/images/awards/bee-level1.png",
|
||||
}, // Replace with actual paths
|
||||
{
|
||||
id: 2,
|
||||
name: "Supplier Development Excellence",
|
||||
year: 2022,
|
||||
awardingBody: "Absa Enterprise Development",
|
||||
description:
|
||||
"Acknowledged for impactful participation in the Absa supplier development program.",
|
||||
imageUrl: "/images/awards/absa-supplier.png",
|
||||
},
|
||||
// Add more awards...
|
||||
];
|
||||
|
||||
export const defaultCertifications: Certification[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Salesforce Certified Partner",
|
||||
issuingBody: "Salesforce",
|
||||
logoUrl: "/images/certs/salesforce-partner.png",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "ISO 9001:2015 (Pending/In Progress)",
|
||||
issuingBody: "Relevant Body",
|
||||
details:
|
||||
"Actively working towards certification for quality management systems.",
|
||||
}, // Example of in-progress
|
||||
// Add more certifications
|
||||
];
|
||||
|
||||
export const defaultPartners: Partner[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Absa Bank",
|
||||
logoUrl: "/clients/absa-logo.png",
|
||||
websiteUrl: "https://www.absa.co.za/",
|
||||
}, // Using client logos for partners
|
||||
{
|
||||
id: 2,
|
||||
name: "Salesforce",
|
||||
logoUrl: "/images/certs/salesforce-partner.png",
|
||||
websiteUrl: "https://www.salesforce.com/",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Sybrin",
|
||||
logoUrl: "/clients/sybrin-logo.png",
|
||||
websiteUrl: "https://www.sybrin.com/",
|
||||
},
|
||||
// Add other key strategic partners if distinct from clients
|
||||
];
|
||||
|
||||
export const defaultGalleryImages: GalleryImage[] = [
|
||||
{
|
||||
id: 1,
|
||||
imageUrl: "/images/gallery/team-event-1.jpg",
|
||||
caption: "Annual Team Building Day 2023",
|
||||
alt: "OMS Team at Annual Event",
|
||||
event: "Team Building",
|
||||
date: "Nov 2023",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
imageUrl: "/images/gallery/award-ceremony-1.jpg",
|
||||
caption: "Receiving the Supplier Development Award",
|
||||
alt: "OMS receiving award",
|
||||
event: "Absa Awards Night",
|
||||
date: "Oct 2022",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
imageUrl: "/images/gallery/office-launch.jpg",
|
||||
caption: "Celebrating our new Centurion office opening",
|
||||
alt: "OMS new office launch",
|
||||
event: "Office Launch",
|
||||
date: "May 2023",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
imageUrl: "/images/gallery/client-workshop.jpg",
|
||||
caption: "Collaborative workshop session with a key client",
|
||||
alt: "OMS client workshop",
|
||||
event: "Client Workshop",
|
||||
date: "Sep 2023",
|
||||
},
|
||||
// Add more gallery images
|
||||
];
|
||||
|
||||
export const defaultMilestones: Milestone[] = [
|
||||
{ id: 1, description: "Established Owethu Managed Services", date: "2019" }, // Adjust start date if needed
|
||||
{
|
||||
id: 2,
|
||||
description: "Joined Absa Supplier Development Programme",
|
||||
date: "2020",
|
||||
},
|
||||
{ id: 3, description: "Became an official Salesforce Partner", date: "2022" },
|
||||
{ id: 4, description: "Launched the OBSE Product", date: "Q1 2023" },
|
||||
{ id: 5, description: "Achieved BEE Level 1 Status", date: "Q3 2023" },
|
||||
{
|
||||
id: 6,
|
||||
description: "Opened new Head Office in Centurion",
|
||||
date: "May 2023",
|
||||
},
|
||||
// Add more significant milestones
|
||||
];
|
||||
361
app/achievements/page.tsx
Normal file
@ -0,0 +1,361 @@
|
||||
// /app/achievements/page.tsx
|
||||
import Image from "next/image";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
FaAward,
|
||||
FaCertificate,
|
||||
FaHandshake,
|
||||
FaCamera,
|
||||
FaTrophy,
|
||||
FaStar,
|
||||
FaExternalLinkAlt,
|
||||
} from "react-icons/fa";
|
||||
import { COLORS } from "@/constants"; // Assuming COLORS is available
|
||||
// import HeroSection from "../_components/HeroSection"; // Reuse HeroSection component
|
||||
|
||||
// Import the placeholder data
|
||||
import {
|
||||
defaultAwards,
|
||||
defaultCertifications,
|
||||
defaultPartners,
|
||||
defaultGalleryImages,
|
||||
defaultMilestones,
|
||||
} from "./_data/achievementsDefaults"; // Adjust path as needed
|
||||
import CallToActionSection from "../(website)/_components/CallToActionSection";
|
||||
|
||||
// SEO Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Achievements & Recognition | Owethu Managed Services (OMS)",
|
||||
description:
|
||||
"Explore the awards, certifications, partnerships, and milestones achieved by OMS. See our commitment to excellence and empowerment.",
|
||||
keywords: [
|
||||
"OMS achievements",
|
||||
"Owethu Managed Services awards",
|
||||
"BEE Level 1",
|
||||
"Salesforce Partner",
|
||||
"company milestones",
|
||||
"IT company recognition",
|
||||
"supplier development program",
|
||||
"South Africa IT awards",
|
||||
"OMS gallery",
|
||||
"OMS partnerships",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Achievements & Recognition | OMS",
|
||||
description:
|
||||
"Discover OMS's journey of success, awards, and key partnerships.",
|
||||
url: "https://oms.cvevolve.com/achievements", // Update with the final URL
|
||||
// Add an appropriate OG image URL if available
|
||||
// images: [{ url: '/images/oms-achievements-og.png', width: 1200, height: 630, alt: 'OMS Achievements' }],
|
||||
locale: "en_ZA",
|
||||
type: "website",
|
||||
},
|
||||
// Add twitter card if desired
|
||||
};
|
||||
|
||||
export default function AchievementsPage() {
|
||||
// In a real app, fetch data here using functions like getAchievements(), getAwards(), etc.
|
||||
const awards = defaultAwards;
|
||||
const certifications = defaultCertifications;
|
||||
const partners = defaultPartners;
|
||||
const galleryImages = defaultGalleryImages;
|
||||
const milestones = defaultMilestones;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 1. Hero Section */}
|
||||
{/* <HeroSection
|
||||
title={
|
||||
<>
|
||||
Our Journey & <br className="hidden md:block" />
|
||||
Recognition
|
||||
</>
|
||||
}
|
||||
subtitle="Celebrating our milestones, awards, and the partnerships that drive our success."
|
||||
// Optional button:
|
||||
// buttonText="Explore Our Services"
|
||||
// buttonHref="/services"
|
||||
imageUrl="/images/hero/achievements-hero.jpg" // Replace with a suitable hero image (e.g., abstract success, team celebration)
|
||||
/> */}
|
||||
|
||||
{/* 2. Introduction (Optional) */}
|
||||
<section className="py-12 md:py-16 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<p className="text-lg md:text-xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed">
|
||||
At Owethu Managed Services, we are proud of the progress we've
|
||||
made and the recognition we've earned. These achievements
|
||||
reflect our commitment to excellence, empowerment, and delivering
|
||||
value to our clients and community.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Awards Section */}
|
||||
{awards && awards.length > 0 && (
|
||||
<section className="py-16 md:py-20 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-center text-gray-900 dark:text-white mb-12">
|
||||
<FaAward
|
||||
className="inline-block mr-3 mb-1"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Awards & Accolades
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{awards.map((award) => (
|
||||
<div
|
||||
key={award.id}
|
||||
className="bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md flex flex-col items-center text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
{award.imageUrl && (
|
||||
<div className="relative h-20 w-20 mb-4">
|
||||
<Image
|
||||
src={award.imageUrl}
|
||||
alt={`${award.name} Logo`}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!award.imageUrl && (
|
||||
<FaTrophy
|
||||
className="text-4xl mb-4"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
)}
|
||||
<h3 className="text-xl font-semibold mb-1 font-poppins text-gray-900 dark:text-white">
|
||||
{award.name}
|
||||
</h3>
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
|
||||
{award.awardingBody} - {award.year}
|
||||
</p>
|
||||
{award.description && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{award.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 4. Certifications & Partnerships Section */}
|
||||
{((certifications && certifications.length > 0) ||
|
||||
(partners && partners.length > 0)) && (
|
||||
<section className="py-16 md:py-20 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-center text-gray-900 dark:text-white mb-12">
|
||||
<FaCertificate
|
||||
className="inline-block mr-3 mb-1"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Certifications &{" "}
|
||||
<FaHandshake
|
||||
className="inline-block ml-2 mr-3 mb-1"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Partnerships
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
{/* Certifications Column */}
|
||||
{certifications && certifications.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold font-poppins text-gray-800 dark:text-gray-100 mb-6 text-center md:text-left">
|
||||
Our Certifications
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
{certifications.map((cert) => (
|
||||
<div
|
||||
key={cert.id}
|
||||
className="flex items-center bg-gray-50 dark:bg-gray-800 p-4 rounded-lg shadow-sm"
|
||||
>
|
||||
{cert.logoUrl && (
|
||||
<div className="relative h-12 w-16 mr-4 flex-shrink-0">
|
||||
<Image
|
||||
src={cert.logoUrl}
|
||||
alt={`${cert.name} Logo`}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!cert.logoUrl && (
|
||||
<FaCertificate
|
||||
className="text-2xl mr-4 flex-shrink-0"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white">
|
||||
{cert.name}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{cert.issuingBody}
|
||||
</p>
|
||||
{cert.details && (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300 mt-1">
|
||||
{cert.details}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Partnerships Column */}
|
||||
{partners && partners.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold font-poppins text-gray-800 dark:text-gray-100 mb-6 text-center md:text-left">
|
||||
Key Partners
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-6 items-center">
|
||||
{partners.map((partner) => (
|
||||
<div key={partner.id} className="text-center group">
|
||||
<a
|
||||
href={partner.websiteUrl || "#"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`relative block h-16 md:h-20 filter grayscale group-hover:grayscale-0 transition duration-300 ${
|
||||
!partner.websiteUrl ? "cursor-default" : ""
|
||||
}`}
|
||||
title={partner.name}
|
||||
>
|
||||
<Image
|
||||
src={partner.logoUrl}
|
||||
alt={`${partner.name} Logo`}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
/>
|
||||
</a>
|
||||
{partner.websiteUrl && (
|
||||
<a
|
||||
href={partner.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-gray-500 dark:text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity duration-300 inline-flex items-center mt-1"
|
||||
>
|
||||
Visit <FaExternalLinkAlt className="ml-1 w-2 h-2" />
|
||||
</a>
|
||||
)}
|
||||
{!partner.websiteUrl && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 mt-1 inline-block">
|
||||
{partner.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{partners.length > 6 && (
|
||||
<p className="text-center text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||
And more...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 5. Milestones Section */}
|
||||
{milestones && milestones.length > 0 && (
|
||||
<section className="py-16 md:py-20 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="container mx-auto px-6 max-w-3xl">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-center text-gray-900 dark:text-white mb-12">
|
||||
<FaStar
|
||||
className="inline-block mr-3 mb-1"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Key Milestones
|
||||
</h2>
|
||||
<div
|
||||
className="relative pl-8 border-l-2 border-gold-500 dark:border-gold-400"
|
||||
style={{ borderColor: COLORS.primary }}
|
||||
>
|
||||
{milestones.map((milestone) => (
|
||||
<div key={milestone.id} className="mb-8 relative">
|
||||
<div
|
||||
className="absolute -left-[1.6rem] top-1 w-6 h-6 bg-gold-500 rounded-full border-4 border-white dark:border-gray-800 dark:bg-gold-400"
|
||||
style={{
|
||||
backgroundColor: COLORS.primary,
|
||||
borderColor:
|
||||
bgColor === "dark"
|
||||
? "#1f2937"
|
||||
: "#f9fafb" /* Adjust based on actual dark/light bg */,
|
||||
}}
|
||||
></div>
|
||||
<p
|
||||
className="text-sm font-semibold text-gold-600 dark:text-gold-400"
|
||||
style={{ color: COLORS.primary }}
|
||||
>
|
||||
{milestone.date}
|
||||
</p>
|
||||
<p className="text-md text-gray-700 dark:text-gray-300">
|
||||
{milestone.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 6. Gallery Section */}
|
||||
{galleryImages && galleryImages.length > 0 && (
|
||||
<section className="py-16 md:py-20 bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-poppins text-center text-gray-900 dark:text-white mb-12">
|
||||
<FaCamera
|
||||
className="inline-block mr-3 mb-1"
|
||||
style={{ color: COLORS.primary }}
|
||||
/>{" "}
|
||||
Moments & Memories
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-6">
|
||||
{galleryImages.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="group relative aspect-square overflow-hidden rounded-lg shadow-md cursor-pointer"
|
||||
>
|
||||
{/* Consider adding a Lightbox library here for better image viewing */}
|
||||
<Image
|
||||
src={image.imageUrl}
|
||||
alt={image.alt}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="transform transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
{/* Overlay for caption */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-3 md:p-4">
|
||||
<p className="text-white text-xs md:text-sm font-semibold line-clamp-2">
|
||||
{image.caption || image.alt}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 7. Optional CTA Section */}
|
||||
<CallToActionSection
|
||||
title="Partner with Proven Expertise"
|
||||
subtitle="Let our recognized capabilities help you achieve your technology goals."
|
||||
buttonText="Discuss Your Project"
|
||||
buttonHref="/contact"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to determine background color for milestone border (if needed)
|
||||
// This is a simplified check, adjust based on how you handle theme toggling
|
||||
const bgColor =
|
||||
typeof window !== "undefined" &&
|
||||
document.documentElement.classList.contains("dark")
|
||||
? "dark"
|
||||
: "light";
|
||||
19
app/api/vacancies/[slug]/route.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { demoVacancies } from "@/lib/demo-data/vacancies";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
const { slug } = await params;
|
||||
// In a real application, you would fetch this data from your CMS (Directus)
|
||||
const vacancy = demoVacancies.find(
|
||||
(v) => v.slug === slug && v.status === "Open"
|
||||
);
|
||||
|
||||
if (!vacancy) {
|
||||
return NextResponse.json({ message: "Vacancy not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(vacancy);
|
||||
}
|
||||
9
app/api/vacancies/route.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { demoVacancies } from "@/lib/demo-data/vacancies";
|
||||
|
||||
export async function GET() {
|
||||
// In a real application, you would fetch this data from your CMS (Directus)
|
||||
// For now, we use the demo data
|
||||
const openVacancies = demoVacancies.filter((v) => v.status === "Open");
|
||||
return NextResponse.json(openVacancies);
|
||||
}
|
||||
@ -130,6 +130,21 @@
|
||||
.animation-delay-600 {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
@keyframes marquee-continuous {
|
||||
0% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
.animate-marquee-continuous {
|
||||
animation: marquee-continuous linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
.group:hover .animate-marquee-continuous {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
/* Optional scale animation for background */
|
||||
@keyframes floatSlightly {
|
||||
@ -193,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;
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ import { Poppins } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import ChatbotWidget from "@/components/ChatbotWidget";
|
||||
import { ThemeProvider } from "@/providers/theme-provider";
|
||||
import Script from "next/script";
|
||||
|
||||
const poppins = Poppins({
|
||||
subsets: ["latin"],
|
||||
@ -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
|
||||
@ -95,8 +97,15 @@ export default function RootLayout({
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
<ChatbotWidget />
|
||||
</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>
|
||||
);
|
||||
|
||||
@ -20,22 +20,25 @@ const BlogPostCard: React.FC<BlogPostCardProps> = ({
|
||||
author,
|
||||
date,
|
||||
}) => {
|
||||
console.log("BlogPostCard Props:", { imageUrl });
|
||||
|
||||
return (
|
||||
<Link href={`/tech-talk/${slug}`} passHref>
|
||||
<div className="group bg-card border border-border rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 ease-in-out flex flex-col h-full transform hover:-translate-y-1">
|
||||
{/* Image Container */}
|
||||
<div className="relative w-full h-48 overflow-hidden">
|
||||
<div className="relative w-full aspect-video overflow-hidden">
|
||||
{imageUrl ? (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={title}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="transition-transform duration-500 group-hover:scale-105"
|
||||
fill // Use fill instead of layout
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105" // Use object-cover class
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
{/* Optional: Subtle overlay on hover */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
) : (
|
||||
// Optional: Placeholder if no image
|
||||
<div className="w-full h-full bg-muted flex items-center justify-center">
|
||||
<span className="text-muted-foreground text-sm">No Image</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
// 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;
|
||||
@ -1,40 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import React, { useActionState, useEffect, useRef } from "react";
|
||||
import { useFormStatus } from "react-dom";
|
||||
import React, { useEffect, useActionState } from "react"; // Removed useRef
|
||||
import { useForm } from "react-hook-form"; // Removed SubmitHandler
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { submitContactForm, ContactFormState } from "@/actions/contact";
|
||||
import Button from "@/components/ui/Button"; // Use your existing Button component
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
// Submit button component with pending state
|
||||
function SubmitButton() {
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<Button type="submit" variant="primary" size="lg" disabled={pending}>
|
||||
{pending ? "Sending..." : "Send Message"}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
// Zod schema for contact form validation
|
||||
const contactSchema = z.object({
|
||||
name: z.string().min(2, "Full name must be at least 2 characters"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
subject: z.string().min(3, "Subject must be at least 3 characters"),
|
||||
message: z.string().min(10, "Message must be at least 10 characters"),
|
||||
});
|
||||
|
||||
type ContactFormData = z.infer<typeof contactSchema>;
|
||||
|
||||
// The main contact form component
|
||||
export default function ContactForm() {
|
||||
// React Hook Form setup
|
||||
const {
|
||||
register,
|
||||
formState: { errors, isValid },
|
||||
reset,
|
||||
} = useForm<ContactFormData>({
|
||||
resolver: zodResolver(contactSchema),
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
const initialState: ContactFormState = {
|
||||
message: null,
|
||||
errors: {},
|
||||
success: false,
|
||||
};
|
||||
const [state, dispatch] = useActionState(submitContactForm, initialState);
|
||||
const formRef = useRef<HTMLFormElement>(null); // Ref to reset the form
|
||||
const [state, formAction] = useActionState(submitContactForm, initialState); // Renamed dispatch to formAction for clarity
|
||||
|
||||
// Reset form on successful submission
|
||||
// Reset form when server action reports success
|
||||
useEffect(() => {
|
||||
if (state.success) {
|
||||
formRef.current?.reset();
|
||||
reset();
|
||||
}
|
||||
}, [state.success]);
|
||||
}, [state.success, reset]);
|
||||
|
||||
// Removed onSubmit handler
|
||||
|
||||
// Pass formAction directly to the form's action prop
|
||||
// Remove onSubmit={handleSubmit(onSubmit)}
|
||||
return (
|
||||
<form ref={formRef} action={dispatch} className="space-y-6">
|
||||
{/* Name Input */}
|
||||
<form action={formAction} className="space-y-6">
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
@ -43,24 +57,40 @@ export default function ContactForm() {
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
className="block w-full px-4 py-2 border border-border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground"
|
||||
type="text"
|
||||
{...register("name")}
|
||||
aria-invalid={errors.name ? "true" : "false"}
|
||||
aria-describedby="name-error"
|
||||
className={`block w-full px-4 py-2 border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground dark:bg-gray-800 dark:text-gray-200 dark:border-gray-600 dark:placeholder-gray-400 ${
|
||||
errors.name
|
||||
? "border-destructive"
|
||||
: "border-border dark:border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
<div id="name-error" aria-live="polite" aria-atomic="true">
|
||||
{/* Client-side error */}
|
||||
{errors.name && (
|
||||
<p
|
||||
id="name-error"
|
||||
role="alert"
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
{/* Server-side error */}
|
||||
{state.errors?.name &&
|
||||
state.errors.name.map((error: string) => (
|
||||
<p className="mt-1 text-sm text-destructive" key={error}>
|
||||
{error}
|
||||
state.errors.name.map((err: string) => (
|
||||
<p
|
||||
key={err}
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{err}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Input */}
|
||||
{/* Email Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
@ -69,24 +99,38 @@ export default function ContactForm() {
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
className="block w-full px-4 py-2 border border-border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground"
|
||||
type="email"
|
||||
{...register("email")}
|
||||
aria-invalid={errors.email ? "true" : "false"}
|
||||
aria-describedby="email-error"
|
||||
className={`block w-full px-4 py-2 border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground dark:bg-gray-800 dark:text-gray-200 dark:border-gray-600 dark:placeholder-gray-400 ${
|
||||
errors.email
|
||||
? "border-destructive"
|
||||
: "border-border dark:border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
<div id="email-error" aria-live="polite" aria-atomic="true">
|
||||
{errors.email && (
|
||||
<p
|
||||
id="email-error"
|
||||
role="alert"
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{errors.email.message}
|
||||
</p>
|
||||
)}
|
||||
{state.errors?.email &&
|
||||
state.errors.email.map((error: string) => (
|
||||
<p className="mt-1 text-sm text-destructive" key={error}>
|
||||
{error}
|
||||
state.errors.email.map((err: string) => (
|
||||
<p
|
||||
key={err}
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{err}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subject Input */}
|
||||
{/* Subject Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="subject"
|
||||
@ -95,24 +139,38 @@ export default function ContactForm() {
|
||||
Subject
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
className="block w-full px-4 py-2 border border-border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground"
|
||||
type="text"
|
||||
{...register("subject")}
|
||||
aria-invalid={errors.subject ? "true" : "false"}
|
||||
aria-describedby="subject-error"
|
||||
className={`block w-full px-4 py-2 border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground dark:bg-gray-800 dark:text-gray-200 dark:border-gray-600 dark:placeholder-gray-400 ${
|
||||
errors.subject
|
||||
? "border-destructive"
|
||||
: "border-border dark:border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
<div id="subject-error" aria-live="polite" aria-atomic="true">
|
||||
{errors.subject && (
|
||||
<p
|
||||
id="subject-error"
|
||||
role="alert"
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{errors.subject.message}
|
||||
</p>
|
||||
)}
|
||||
{state.errors?.subject &&
|
||||
state.errors.subject.map((error: string) => (
|
||||
<p className="mt-1 text-sm text-destructive" key={error}>
|
||||
{error}
|
||||
state.errors.subject.map((err: string) => (
|
||||
<p
|
||||
key={err}
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{err}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message Textarea */}
|
||||
{/* Message Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="message"
|
||||
@ -122,38 +180,65 @@ export default function ContactForm() {
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows={5}
|
||||
required
|
||||
className="block w-full px-4 py-2 border border-border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground"
|
||||
{...register("message")}
|
||||
aria-invalid={errors.message ? "true" : "false"}
|
||||
aria-describedby="message-error"
|
||||
></textarea>
|
||||
<div id="message-error" aria-live="polite" aria-atomic="true">
|
||||
className={`block w-full px-4 py-2 border rounded-lg shadow-sm focus:ring-primary focus:border-primary sm:text-sm bg-input text-foreground placeholder-muted-foreground resize-vertical dark:bg-gray-800 dark:text-gray-200 dark:border-gray-600 dark:placeholder-gray-400 ${
|
||||
errors.message
|
||||
? "border-destructive"
|
||||
: "border-border dark:border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
{errors.message && (
|
||||
<p
|
||||
id="message-error"
|
||||
role="alert"
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{errors.message.message}
|
||||
</p>
|
||||
)}
|
||||
{state.errors?.message &&
|
||||
state.errors.message.map((error: string) => (
|
||||
<p className="mt-1 text-sm text-destructive" key={error}>
|
||||
{error}
|
||||
state.errors.message.map((err: string) => (
|
||||
<p
|
||||
key={err}
|
||||
className="mt-1 text-sm text-destructive dark:text-red-400"
|
||||
>
|
||||
{err}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* General Form Message (Success or Error) */}
|
||||
<div id="form-response" aria-live="polite" aria-atomic="true">
|
||||
{/* General Form Response */}
|
||||
{state.message && (
|
||||
<p
|
||||
className={`text-sm ${
|
||||
state.success ? "text-green-600" : "text-destructive"
|
||||
state.success
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-destructive dark:text-red-400"
|
||||
}`}
|
||||
>
|
||||
{state.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
{/* isSubmitting from useFormState is now implicitly handled by the form action */}
|
||||
<div className="pt-2">
|
||||
<SubmitButton />
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
// Disable based on client-side validation only before submission starts
|
||||
// The form handles disabling during submission automatically
|
||||
disabled={!isValid}
|
||||
// aria-disabled={pending} // You might need to import useFormStatus for pending state
|
||||
>
|
||||
{/* {pending ? "Sending..." : "Send Message"} // Use useFormStatus for pending state */}
|
||||
Send Message{" "}
|
||||
{/* Simplified button text, pending state handled by browser/React */}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -69,7 +70,7 @@ const Footer = () => {
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/services"
|
||||
href="/services/resource-augmentation"
|
||||
className="text-sm text-[var(--oms-white)]/80 hover:text-primary transition-colors"
|
||||
>
|
||||
Services
|
||||
@ -77,20 +78,20 @@ const Footer = () => {
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/products"
|
||||
href="/obse"
|
||||
className="text-sm text-[var(--oms-white)]/80 hover:text-primary transition-colors"
|
||||
>
|
||||
Products
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
{/* <li>
|
||||
<Link
|
||||
href="/join-us"
|
||||
className="text-sm text-[var(--oms-white)]/80 hover:text-primary transition-colors"
|
||||
>
|
||||
Join Our Team
|
||||
</Link>
|
||||
</li>
|
||||
</li> */}
|
||||
<li>
|
||||
<Link
|
||||
href="/contact"
|
||||
@ -106,16 +107,16 @@ const Footer = () => {
|
||||
<div>
|
||||
<h5 className="text-lg font-semibold mb-4 text-primary">Contact</h5>
|
||||
<address className="text-sm text-[var(--oms-white)]/80 mb-2 not-italic">
|
||||
Unit 10 B Centuria Park
|
||||
<br />
|
||||
265 Von Willich Avenue
|
||||
<br />
|
||||
Die Hoewes, Centurion, 0159
|
||||
<br />
|
||||
(Unit 10 B Centuria Park)
|
||||
</address>
|
||||
<p className="text-sm text-[var(--oms-white)]/80 mb-2">
|
||||
Phone:{" "}
|
||||
<a href="tel:+27120513282" className="hover:text-primary">
|
||||
(012) 051 3282
|
||||
<a href="tel:+27684855721" className="hover:text-primary">
|
||||
+27 68 485 5721
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-sm text-[var(--oms-white)]/80 mb-2">
|
||||
@ -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>
|
||||
|
||||
@ -157,20 +170,43 @@ const Footer = () => {
|
||||
</p>
|
||||
<form className="flex flex-col sm:flex-row gap-2">
|
||||
{/* Input needs dark background styles */}
|
||||
<div className="relative max-w-md mx-auto w-full">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
aria-label="Email for newsletter"
|
||||
// Use dark variable for input bg/border, bright for text
|
||||
className="flex-grow px-4 py-2 rounded-lg bg-[var(--dark-input)] border border-[var(--dark-border)] text-[var(--dark-foreground)] placeholder:text-[var(--dark-muted-foreground)] focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-[var(--oms-black)]" // Ring offset needs dark bg
|
||||
className="
|
||||
w-full
|
||||
pr-24
|
||||
px-4 py-2
|
||||
rounded-lg
|
||||
bg-[var(--dark-input)]
|
||||
border border-[var(--dark-border)]
|
||||
text-[var(--dark-foreground)]
|
||||
placeholder:text-[var(--dark-muted-foreground)]
|
||||
focus:outline-none focus:ring-2 focus:ring-ring
|
||||
focus:ring-offset-2 focus:ring-offset-[var(--oms-black)]
|
||||
"
|
||||
/>
|
||||
{/* Keep button styling primary */}
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-primary text-primary-foreground px-4 py-2 rounded-lg font-semibold hover:bg-opacity-90 transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-[var(--oms-black)]" // Ring offset needs dark bg
|
||||
className="
|
||||
absolute top-0 right-0
|
||||
h-full
|
||||
px-4
|
||||
rounded-r-lg
|
||||
bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)]
|
||||
text-primary-foreground
|
||||
font-semibold
|
||||
hover:bg-opacity-90
|
||||
transition-colors
|
||||
focus:outline-none focus:ring-2 focus:ring-ring
|
||||
focus:ring-offset-2 focus:ring-offset-[var(--oms-black)]
|
||||
"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/* Badges - Use a subtle dark bg */}
|
||||
<div className="mt-6 space-x-2 sm:space-x-4">
|
||||
@ -193,19 +229,29 @@ const Footer = () => {
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
{/* Links still hover to primary */}
|
||||
<Link
|
||||
|
||||
{/* <Link
|
||||
href="/privacy-policy"
|
||||
className="hover:text-primary transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<Link
|
||||
href="/paia-popia"
|
||||
</Link> */}
|
||||
|
||||
|
||||
<a
|
||||
href="/Privacy-Policy/Recruitment-Privacy-Policy.pdf"
|
||||
className="hover:text-primary transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a
|
||||
href="/Privacy-Policy/Data-Privacy-Statement-for-Candidates.pdf"
|
||||
className="hover:text-primary transition-colors"
|
||||
>
|
||||
PAIA & POPIA
|
||||
</Link>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@ -213,3 +259,4 @@ const Footer = () => {
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
//Data-Privacy-Statement-for-Candidates
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
// components/Header.tsx (Server Component)
|
||||
import React from "react";
|
||||
import { auth } from "@/auth"; // Only need auth (for session) from here now
|
||||
|
||||
import HeaderClient from "./HeaderClient";
|
||||
import { handleSignInAction, handleSignOutAction } from "@/actions/auth-action";
|
||||
|
||||
const Header = async () => {
|
||||
// Fetch session data on the server
|
||||
const session = await auth();
|
||||
|
||||
// Pass the session data and YOUR Server Actions as props
|
||||
return (
|
||||
<HeaderClient
|
||||
session={session}
|
||||
handleSignIn={handleSignInAction} // Pass YOUR sign-in Server Action
|
||||
handleSignOut={handleSignOutAction} // Pass YOUR sign-out Server Action
|
||||
/>
|
||||
);
|
||||
return <HeaderClient />;
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
@ -4,207 +4,165 @@
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
// Use usePathname only if needed for active state logic not shown here
|
||||
// import { usePathname } from "next/navigation";
|
||||
import { usePathname } from "next/navigation"; // Import usePathname
|
||||
import {
|
||||
FiChevronDown,
|
||||
FiClipboard,
|
||||
FiArrowRight,
|
||||
FiMenu,
|
||||
FiX,
|
||||
FiLogIn, // Import the login icon
|
||||
FiUsers,
|
||||
FiCpu,
|
||||
FiBox,
|
||||
FiLayers
|
||||
} from "react-icons/fi";
|
||||
import ThemeToggle from "./ThemeToggle"; // Assuming ThemeToggle component exists
|
||||
import type { Session } from "@auth/core/types"; // Import Session type for props
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
|
||||
const omsLogoUrl = "/oms-logo.svg"; // Ensure this is in your /public folder
|
||||
|
||||
// --- Basic Dropdown Component ---
|
||||
// (Keep the DropdownMenu and DropdownLink component definitions here as before)
|
||||
type DropdownMenuProps = {
|
||||
trigger: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
menuClasses?: string;
|
||||
};
|
||||
const DropdownMenu = ({
|
||||
trigger,
|
||||
children,
|
||||
menuClasses = "w-48",
|
||||
}: DropdownMenuProps) => (
|
||||
// Using group-focus-within for better keyboard/touch accessibility
|
||||
<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 group-focus-within:rotate-180" />
|
||||
</button>
|
||||
<div
|
||||
className={`
|
||||
absolute left-0 mt-2 ${menuClasses} origin-top-left rounded-md shadow-lg z-50
|
||||
bg-card border border-border {/* USE SEMANTIC VARS */}
|
||||
opacity-0 invisible group-hover:opacity-100 group-hover:visible group-focus-within:opacity-100 group-focus-within:visible transition-all duration-200
|
||||
`}
|
||||
>
|
||||
<div className="py-1">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const omsLogoUrl = "/oms-logo.svg";
|
||||
|
||||
type DropdownLinkProps = {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void; // Added onClick for mobile menu closure
|
||||
onClick?: () => void;
|
||||
};
|
||||
const DropdownLink = ({ href, children, onClick }: DropdownLinkProps) => (
|
||||
<Link
|
||||
href={href}
|
||||
onClick={onClick} // Call onClick if provided
|
||||
className="block w-full text-left px-4 py-2 text-sm text-card-foreground hover:bg-secondary hover:text-primary" // USE SEMANTIC VARS
|
||||
onClick={onClick}
|
||||
className="block w-full text-left px-4 py-2 text-sm text-card-foreground hover:bg-secondary hover:text-primary transition-colors duration-150"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
// --- End Dropdown Component ---
|
||||
|
||||
// --- Define Props for the Client Component ---
|
||||
type HeaderClientProps = {
|
||||
session: Session | null; // Accept session from the server component
|
||||
handleSignIn: () => void; // Pass sign-in handler
|
||||
handleSignOut: () => void; // Pass sign-out handler
|
||||
};
|
||||
|
||||
// --- The Main Client Component ---
|
||||
const HeaderClient = ({
|
||||
session,
|
||||
handleSignIn,
|
||||
handleSignOut,
|
||||
}: HeaderClientProps) => {
|
||||
// --- Client-side state and hooks ---
|
||||
const HeaderClient = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
// const pathname = usePathname() || "/"; // Uncomment if needed
|
||||
// const isActive = (path: string) => pathname === path; // Uncomment if needed
|
||||
|
||||
const toggleMenu = () => setIsMenuOpen((open) => !open);
|
||||
const handleMobileLinkClick = () => setIsMenuOpen(false);
|
||||
|
||||
const currentLogo = omsLogoUrl;
|
||||
const [servicesDropdownOpen, setServicesDropdownOpen] = useState(false);
|
||||
const [productsDropdownOpen, setProductsDropdownOpen] = useState(false);
|
||||
//const [joinUsDropdownOpen, setJoinUsDropdownOpen] = useState(false);
|
||||
|
||||
const handleServicesMouseEnter = () => setServicesDropdownOpen(true);
|
||||
const handleServicesMouseLeave = () => setServicesDropdownOpen(false);
|
||||
|
||||
const handleProductsMouseEnter = () => setProductsDropdownOpen(true);
|
||||
const handleProductsMouseLeave = () => setProductsDropdownOpen(false);
|
||||
|
||||
// const handleJoinUsMouseEnter = () => setJoinUsDropdownOpen(true);
|
||||
// const handleJoinUsMouseLeave = () => setJoinUsDropdownOpen(false);
|
||||
|
||||
const pathname = usePathname(); // Get current path
|
||||
|
||||
// Helper function to check if a path is active (exact match for simple links, startsWith for base paths)
|
||||
const isActive = (href: string, exact = false) => {
|
||||
if (exact) {
|
||||
return pathname === href;
|
||||
}
|
||||
// Handle root path specifically
|
||||
if (href === "/") {
|
||||
return pathname === "/";
|
||||
}
|
||||
return pathname.startsWith(href);
|
||||
};
|
||||
|
||||
const megaMenuTriggerClasses = `
|
||||
relative inline-flex items-center text-sm font-medium text-primary-foreground
|
||||
hover:opacity-90 transition-opacity duration-150 ease-in-out pb-1
|
||||
after:content-[''] after:absolute after:left-0 after:bottom-0 after:h-[2px]
|
||||
after:w-0 after:bg-primary-foreground/80 after:transition-all after:duration-300 after:ease-out
|
||||
`;
|
||||
|
||||
const megaMenuItemClasses = `
|
||||
flex items-center p-3 -m-3 rounded-lg
|
||||
hover:bg-secondary transition-colors duration-150 ease-in-out
|
||||
`;
|
||||
const megaMenuIconClasses = `flex-shrink-0 h-6 w-6 text-primary mr-4`;
|
||||
const megaMenuTextWrapperClasses = `text-sm`;
|
||||
const megaMenuTitleClasses = `font-semibold text-card-foreground`;
|
||||
|
||||
return (
|
||||
// Use semantic variables for header background and border
|
||||
<header className="sticky top-0 z-50 shadow-md bg-background border-b border-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}
|
||||
src={omsLogoUrl}
|
||||
alt="OMS Logo"
|
||||
width={40}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
{/* Use semantic variable for text color */}
|
||||
<span className="text-xl font-bold text-foreground hidden sm:inline">
|
||||
Owethu Managed Services
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center space-x-6 lg:space-x-8">
|
||||
{/* Use semantic variables for text, hover uses primary */}
|
||||
<Link
|
||||
href="/"
|
||||
className={`text-sm font-medium text-foreground/80 hover:text-primary transition`}
|
||||
className={`text-sm font-medium transition ${
|
||||
isActive("/")
|
||||
? "text-primary"
|
||||
: "text-foreground/80 hover:text-primary"
|
||||
}`} // Apply active class
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<Link
|
||||
href="/tech-talk"
|
||||
className={`text-sm font-medium text-foreground/80 hover:text-primary transition`}
|
||||
className={`text-sm font-medium transition ${
|
||||
isActive("/tech-talk")
|
||||
? "text-primary"
|
||||
: "text-foreground/80 hover:text-primary"
|
||||
}`} // Apply active class
|
||||
>
|
||||
Tech Talk
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className={`text-sm font-medium text-foreground/80 hover:text-primary transition`}
|
||||
className={`text-sm font-medium transition ${
|
||||
isActive("/about")
|
||||
? "text-primary"
|
||||
: "text-foreground/80 hover:text-primary"
|
||||
}`} // Apply active class
|
||||
>
|
||||
About Us
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className={`text-sm font-medium text-foreground/80 hover:text-primary transition`}
|
||||
className={`text-sm font-medium transition ${
|
||||
isActive("/contact")
|
||||
? "text-primary"
|
||||
: "text-foreground/80 hover:text-primary"
|
||||
}`} // Apply active class
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
{/* Desktop Utilities */}
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<ThemeToggle />
|
||||
|
||||
{/* Request Demo Button */}
|
||||
<Link
|
||||
href="/request-demo"
|
||||
className="flex items-center text-sm font-medium bg-primary text-primary-foreground px-3 py-1.5 rounded-lg hover:bg-opacity-90 transition-colors"
|
||||
href="/contact"
|
||||
className="flex items-center text-sm font-medium bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-primary-foreground px-3 py-1.5 rounded-lg hover:bg-opacity-90 transition-colors"
|
||||
title="Request a Demo"
|
||||
>
|
||||
<FiClipboard className="w-4 h-4 mr-1.5" />
|
||||
Request Demo
|
||||
Request OBSE Demo
|
||||
</Link>
|
||||
|
||||
{/* --- Auth Section --- */}
|
||||
{session?.user ? (
|
||||
// Logged In: User Dropdown
|
||||
<DropdownMenu
|
||||
menuClasses="w-40 right-0 left-auto" // Align dropdown to the right
|
||||
trigger={
|
||||
<div className="flex items-center space-x-2 cursor-pointer hover:opacity-80 transition">
|
||||
<Image
|
||||
src={session.user.image || "/default-avatar.png"} // Provide a fallback avatar
|
||||
alt={session.user.name || "User"}
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-full ring-1 ring-primary ring-offset-2 ring-offset-background" // Added offset
|
||||
/>
|
||||
{/* Optionally hide name on smaller desktop screens */}
|
||||
<span className="text-sm font-medium text-foreground hidden lg:inline">
|
||||
{session.user.name?.split(" ")[0]}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* Sign Out inside the dropdown */}
|
||||
<button
|
||||
onClick={() => handleSignOut()}
|
||||
className="block w-full text-left px-4 py-2 text-sm text-destructive hover:bg-secondary hover:text-destructive transition" // Destructive color
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
// Logged Out: Icon Button to Sign In
|
||||
<button
|
||||
onClick={() => handleSignIn()}
|
||||
className="p-2 rounded-full text-foreground/70 hover:text-primary hover:bg-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background transition-colors"
|
||||
title="Sign In"
|
||||
aria-label="Sign In"
|
||||
>
|
||||
<FiLogIn className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
{/* --- End Auth Section --- */}
|
||||
</div>
|
||||
|
||||
{/* Mobile Buttons */}
|
||||
<div className="md:hidden flex items-center">
|
||||
<ThemeToggle /> {/* Theme toggle remains */}
|
||||
<ThemeToggle />
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
className="text-foreground/60 hover:text-primary focus:outline-none ml-3" // Use semantic muted color, hover primary
|
||||
className="text-foreground/60 hover:text-primary focus:outline-none ml-3"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="mobile-menu"
|
||||
@ -219,72 +177,272 @@ const HeaderClient = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Secondary Row (Desktop Only) */}
|
||||
<div className="bg-primary">
|
||||
{/* Secondary Row w/ Mega Menus */}
|
||||
<div className="bg-[linear-gradient(to_right,#dec750,#f0de7e,#e1c44a)] relative">
|
||||
<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-primary-foreground">
|
||||
<div className="hover:text-opacity-80 transition-opacity">
|
||||
<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>
|
||||
</div>
|
||||
<div className="hover:text-opacity-80 transition-opacity">
|
||||
<DropdownMenu trigger={<span>Products</span>}>
|
||||
<DropdownLink href="/products/obse">OBSE</DropdownLink>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="hover:text-opacity-80 transition-opacity">
|
||||
<DropdownMenu trigger={<span>Join Our Team</span>}>
|
||||
<DropdownLink href="/join-us/vacancies">
|
||||
Vacancies
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/join-us/portal">
|
||||
Recruitment Portal
|
||||
</DropdownLink>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</nav>
|
||||
<Link
|
||||
href="/services"
|
||||
className="flex items-center text-sm font-medium text-primary-foreground hover:text-opacity-80 transition-opacity group"
|
||||
{/* Wrap nav and link in a flex container */}
|
||||
<div className="flex items-center space-x-8 lg:space-x-10">
|
||||
<nav className="flex items-center space-x-8 lg:space-x-10">
|
||||
{/* Services */}
|
||||
<div
|
||||
className={`group ${isActive("/services") ? "active" : ""}`} // Add active class to group
|
||||
onMouseEnter={handleServicesMouseEnter}
|
||||
onMouseLeave={handleServicesMouseLeave}
|
||||
>
|
||||
Explore Our Offerings
|
||||
<FiArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
||||
<button
|
||||
className={`${megaMenuTriggerClasses} ${
|
||||
isActive("/services") ? "after:w-full" : ""
|
||||
}`}
|
||||
>
|
||||
{" "}
|
||||
{/* Apply underline based on active state */}
|
||||
Services
|
||||
<FiChevronDown className="w-4 h-4 ml-1.5 opacity-70 transition-transform duration-200" />
|
||||
</button>
|
||||
<div
|
||||
className={`
|
||||
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]
|
||||
${
|
||||
servicesDropdownOpen
|
||||
? "opacity-100 visible translate-y-0"
|
||||
: ""
|
||||
}
|
||||
transition-all duration-300 ease-out transform
|
||||
`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 max-w-xl">
|
||||
<Link
|
||||
href="/services/resource-augmentation"
|
||||
className={`${megaMenuItemClasses} ${
|
||||
isActive("/services/resource-augmentation")
|
||||
? "text-primary"
|
||||
: ""
|
||||
}`} // Apply active class
|
||||
>
|
||||
<FiUsers className={megaMenuIconClasses} />
|
||||
<div className={megaMenuTextWrapperClasses}>
|
||||
<p className={megaMenuTitleClasses}>
|
||||
Resource Augmentation
|
||||
</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="/services/product-development"
|
||||
className={`${megaMenuItemClasses} ${
|
||||
isActive("/services/product-development")
|
||||
? "text-primary"
|
||||
: ""
|
||||
}`} // Apply active class
|
||||
>
|
||||
<FiCpu className={megaMenuIconClasses} />
|
||||
<div className={megaMenuTextWrapperClasses}>
|
||||
<p className={megaMenuTitleClasses}>
|
||||
Product Development
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Panel */}
|
||||
{/* Products */}
|
||||
<div
|
||||
className={`group ${isActive("/products") ? "active" : ""}`} // Add active class to group
|
||||
onMouseEnter={handleProductsMouseEnter}
|
||||
onMouseLeave={handleProductsMouseLeave}
|
||||
>
|
||||
<button
|
||||
className={`${megaMenuTriggerClasses} ${
|
||||
isActive("/products") ? "after:w-full" : ""
|
||||
}`}
|
||||
>
|
||||
{" "}
|
||||
{/* Apply underline based on active state */}
|
||||
Products
|
||||
<FiChevronDown className="w-4 h-4 ml-1.5 opacity-70 transition-transform duration-200" />
|
||||
</button>
|
||||
<div
|
||||
className={`
|
||||
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]
|
||||
${
|
||||
productsDropdownOpen
|
||||
? "opacity-100 visible translate-y-0"
|
||||
: ""
|
||||
}
|
||||
transition-all duration-300 ease-out transform
|
||||
`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 max-w-xl">
|
||||
<Link
|
||||
href="/obse"
|
||||
className={`${megaMenuItemClasses} ${
|
||||
isActive("/obse") ? "text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
{" "}
|
||||
{/* Apply active class */}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Join Our Team */}
|
||||
|
||||
|
||||
{/* <div
|
||||
className={`group ${isActive("/offerings") ? "active" : ""}`}
|
||||
onMouseEnter={handleOfferingsMouseEnter}
|
||||
onMouseLeave={handleOfferingsMouseLeave}
|
||||
>
|
||||
<button
|
||||
className={`${megaMenuTriggerClasses} ${
|
||||
isActive("/offerings") ? "after:w-full" : ""
|
||||
}`}
|
||||
>
|
||||
Explore Our Offerings
|
||||
<FiChevronDown className="w-4 h-4 ml-1.5 opacity-70 transition-transform duration-200" />
|
||||
</button>
|
||||
<div
|
||||
className={`
|
||||
absolute left-0 top-full w-full shadow-lg z-1000
|
||||
bg-card border-x border-b border-border rounded-b-lg
|
||||
opacity-0 invisible translate-y-[-10px]
|
||||
${offeringsDropdownOpen ? "opacity-100 visible translate-y-0" : ""}
|
||||
transition-all duration-300 ease-out transform
|
||||
`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||
<div className="max-w-md">
|
||||
<Link
|
||||
href="/offerings"
|
||||
className={`${megaMenuItemClasses} ${
|
||||
isActive("/offerings") ? "text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
<FiBox className={megaMenuIconClasses} />
|
||||
<div className={megaMenuTextWrapperClasses}>
|
||||
<p className={megaMenuTitleClasses}>
|
||||
Our Offerings
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</nav>
|
||||
|
||||
{/* ← Here’s the Explore Our Offerings link, back in its original spot
|
||||
<Link
|
||||
href="/services" // Assuming this link goes to the main services page
|
||||
className={`flex items-center text-sm font-medium hover:opacity-80 transition-opacity group ${
|
||||
isActive("/services")
|
||||
? "text-primary"
|
||||
: "text-primary-foreground"
|
||||
}`} // Apply active class
|
||||
>
|
||||
Explore Our Offerings
|
||||
<FiArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
||||
</Link>
|
||||
*/}
|
||||
</div>{" "}
|
||||
{/* Close the new flex container */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div
|
||||
id="mobile-menu"
|
||||
// Use semantic variables for background and border
|
||||
className={`md:hidden absolute top-full left-0 w-full shadow-lg transition-all duration-300 ease-in-out overflow-hidden bg-card border-t border-border ${
|
||||
isMenuOpen
|
||||
? "max-h-[calc(100vh-4rem)] py-4 overflow-y-auto"
|
||||
: "max-h-0 py-0" // Animate height, allow scroll
|
||||
: "max-h-0 py-0"
|
||||
}`}
|
||||
>
|
||||
{/* Use semantic variable for text color */}
|
||||
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 flex flex-col space-y-1 text-foreground">
|
||||
{" "}
|
||||
{/* Reduced space-y */}
|
||||
{/* Mobile Links */}
|
||||
<DropdownLink href="/" onClick={handleMobileLinkClick}>
|
||||
Home
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/tech-talk" onClick={handleMobileLinkClick}>
|
||||
Tech Talk
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/about" onClick={handleMobileLinkClick}>
|
||||
About Us
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/contact" onClick={handleMobileLinkClick}>
|
||||
Contact Us
|
||||
</DropdownLink>
|
||||
<span className="pt-3 pb-1 text-xs uppercase text-muted-foreground">
|
||||
Services
|
||||
</span>
|
||||
@ -294,79 +452,55 @@ const HeaderClient = ({
|
||||
>
|
||||
Resource Augmentation
|
||||
</DropdownLink>
|
||||
<DropdownLink
|
||||
href="/services/project-management"
|
||||
onClick={handleMobileLinkClick}
|
||||
>
|
||||
Project Management
|
||||
</DropdownLink>
|
||||
<DropdownLink
|
||||
href="/services/product-development"
|
||||
onClick={handleMobileLinkClick}
|
||||
>
|
||||
Product Development
|
||||
</DropdownLink>
|
||||
|
||||
<span className="pt-3 pb-1 text-xs uppercase text-muted-foreground">
|
||||
Products
|
||||
</span>
|
||||
<DropdownLink href="/products/obse" onClick={handleMobileLinkClick}>
|
||||
<DropdownLink href="/obse" onClick={handleMobileLinkClick}>
|
||||
OBSE
|
||||
</DropdownLink>
|
||||
<span className="pt-3 pb-1 text-xs uppercase text-muted-foreground">
|
||||
|
||||
{/* 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>
|
||||
<DropdownLink
|
||||
href="/join-us/vacancies"
|
||||
onClick={handleMobileLinkClick}
|
||||
>
|
||||
<DropdownLink href="/vacancies" onClick={handleMobileLinkClick}>
|
||||
Vacancies
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/join-us/portal" onClick={handleMobileLinkClick}>
|
||||
<DropdownLink href="/portal" onClick={handleMobileLinkClick}>
|
||||
Recruitment Portal
|
||||
</DropdownLink>
|
||||
<DropdownLink href="/contact" onClick={handleMobileLinkClick}>
|
||||
Contact Us
|
||||
</DropdownLink>
|
||||
{/* Demo Button */}
|
||||
</DropdownLink> */}
|
||||
|
||||
{/*
|
||||
<span className="pt-3 pb-1 text-xs uppercase text-muted-foreground">
|
||||
Explore Our Offerings
|
||||
</span>
|
||||
<DropdownLink href="/offerings" onClick={handleMobileLinkClick}>
|
||||
Our Offerings
|
||||
</DropdownLink> */}
|
||||
|
||||
<div className="pt-4">
|
||||
<Link
|
||||
href="/request-demo"
|
||||
href="/contact"
|
||||
onClick={handleMobileLinkClick}
|
||||
// Use semantic variables for button style
|
||||
className="flex w-full justify-center items-center text-sm font-medium bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-opacity-90 transition-colors"
|
||||
className="flex w-full justify-center items-center text-sm font-medium bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-primary-foreground px-4 py-2 rounded-lg hover:bg-opacity-90 transition-colors"
|
||||
title="Request a Demo"
|
||||
>
|
||||
<FiClipboard className="w-4 h-4 mr-1.5" />
|
||||
Request Demo
|
||||
<FiClipboard className="w-4 h-4 mr-1.5" /> Request OBSE Demo
|
||||
</Link>
|
||||
</div>
|
||||
{/* Auth Buttons in Mobile Menu */}
|
||||
<div className="pt-4 border-t border-border mt-4">
|
||||
{session?.user ? (
|
||||
// Sign Out Button (Text)
|
||||
<button
|
||||
onClick={() => {
|
||||
handleSignOut();
|
||||
handleMobileLinkClick();
|
||||
}}
|
||||
className="flex items-center w-full text-left px-4 py-2 text-sm font-medium text-destructive hover:bg-secondary transition"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
) : (
|
||||
// Sign In Button (Text or Icon+Text)
|
||||
<button
|
||||
onClick={() => {
|
||||
handleSignIn();
|
||||
handleMobileLinkClick();
|
||||
}}
|
||||
className="flex items-center w-full text-left px-4 py-2 text-sm font-medium text-foreground hover:bg-secondary hover:text-primary transition"
|
||||
>
|
||||
<FiLogIn className="w-4 h-4 mr-2" /> {/* Added icon here too */}
|
||||
Sign In
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-border mt-4"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
@ -374,3 +508,77 @@ const HeaderClient = ({
|
||||
};
|
||||
|
||||
export default HeaderClient;
|
||||
// <div
|
||||
// className={`group ${
|
||||
// isActive("/join-us") ||
|
||||
// isActive("/vacancies") ||
|
||||
// isActive("/portal")
|
||||
// ? "active"
|
||||
// : ""
|
||||
// }`} // Add active class to group (check all related paths)
|
||||
// onMouseEnter={handleJoinUsMouseEnter}
|
||||
// onMouseLeave={handleJoinUsMouseLeave}
|
||||
// >
|
||||
// <button
|
||||
// className={`${megaMenuTriggerClasses} ${
|
||||
// isActive("/join-us") ||
|
||||
// isActive("/vacancies") ||
|
||||
// isActive("/portal")
|
||||
// ? "after:w-full"
|
||||
// : ""
|
||||
// }`}
|
||||
// >
|
||||
// {" "}
|
||||
// {/* Apply underline based on active state */}
|
||||
// Join Our Team
|
||||
// <FiChevronDown className="w-4 h-4 ml-1.5 opacity-70 transition-transform duration-200" />
|
||||
// </button>
|
||||
// <div
|
||||
// className={`
|
||||
// absolute left-0 top-full w-full shadow-lg z-1000
|
||||
// bg-card border-x border-b border-border rounded-b-lg
|
||||
// opacity-0 invisible translate-y-[-10px]
|
||||
// ${
|
||||
// joinUsDropdownOpen
|
||||
// ? "opacity-100 visible translate-y-0"
|
||||
// : ""
|
||||
// }
|
||||
// transition-all duration-300 ease-out transform
|
||||
// `}
|
||||
// >
|
||||
// <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 max-w-xl">
|
||||
// <Link
|
||||
// href="/vacancies"
|
||||
// className={`${megaMenuItemClasses} ${
|
||||
// isActive("/vacancies") ? "text-primary" : ""
|
||||
// }`}
|
||||
// >
|
||||
// {" "}
|
||||
// {/* Apply active class */}
|
||||
// <FiFileText className={megaMenuIconClasses} />
|
||||
// <div className={megaMenuTextWrapperClasses}>
|
||||
// <p className={megaMenuTitleClasses}>
|
||||
// Current Vacancies
|
||||
// </p>
|
||||
// </div>
|
||||
// </Link>
|
||||
// <Link
|
||||
// href="/portal"
|
||||
// className={`${megaMenuItemClasses} ${
|
||||
// isActive("/portal") ? "text-primary" : ""
|
||||
// }`}
|
||||
// >
|
||||
// {" "}
|
||||
// {/* Apply active class */}
|
||||
// <FiUserCheck className={megaMenuIconClasses} />
|
||||
// <div className={megaMenuTextWrapperClasses}>
|
||||
// <p className={megaMenuTitleClasses}>
|
||||
// Recruitment Portal
|
||||
// </p>
|
||||
// </div>
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
@ -164,7 +164,7 @@ const Header = () => {
|
||||
title="Request a Demo"
|
||||
>
|
||||
<FiClipboard className="w-4 h-4 mr-1.5" />
|
||||
Request Demo
|
||||
Request OBSE Demo
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
374
components/VacancyApplicationForm.tsx
Normal file
@ -0,0 +1,374 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useCallback, ChangeEvent, DragEvent } from "react";
|
||||
import { FaUpload, FaFileAlt, FaTimes } from "react-icons/fa";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import Button from "./ui/Button";
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
const ACCEPTED_FILE_TYPES = [
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"text/plain",
|
||||
];
|
||||
|
||||
const applicationSchema = z.object({
|
||||
name: z.string().min(2, "Full name must be at least 2 characters"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
phone: z.string().optional(),
|
||||
linkedin: z.string().url("Invalid URL").optional().or(z.literal("")),
|
||||
coverLetter: z.string().optional(),
|
||||
resume: z
|
||||
.custom<File | null>((val) => val instanceof File, "Resume/CV is required")
|
||||
.refine(
|
||||
(file) => file && file.size <= MAX_FILE_SIZE,
|
||||
`Max file size is 5MB.`
|
||||
)
|
||||
.refine(
|
||||
(file) => file && ACCEPTED_FILE_TYPES.includes(file.type),
|
||||
"Unsupported file format. Please upload PDF, DOC, DOCX, or TXT."
|
||||
),
|
||||
});
|
||||
|
||||
type ApplicationFormData = z.infer<typeof applicationSchema>;
|
||||
|
||||
interface VacancyApplicationFormProps {
|
||||
vacancyId: string;
|
||||
vacancyTitle: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function VacancyApplicationForm({
|
||||
vacancyId,
|
||||
vacancyTitle,
|
||||
onClose,
|
||||
}: VacancyApplicationFormProps) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors, isSubmitting },
|
||||
reset,
|
||||
} = useForm<ApplicationFormData>({
|
||||
resolver: zodResolver(applicationSchema),
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
linkedin: "",
|
||||
coverLetter: "",
|
||||
resume: null,
|
||||
},
|
||||
});
|
||||
|
||||
const resumeFile = watch("resume");
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [submitStatus, setSubmitStatus] = useState<
|
||||
"idle" | "success" | "error"
|
||||
>("idle");
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
setValue("resume", e.target.files[0], { shouldValidate: true });
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const handleDragOver = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!isDragging) setIsDragging(true);
|
||||
},
|
||||
[isDragging]
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
setValue("resume", e.dataTransfer.files[0], { shouldValidate: true });
|
||||
e.dataTransfer.clearData();
|
||||
}
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const removeFile = () => {
|
||||
setValue("resume", null, { shouldValidate: true });
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<ApplicationFormData> = async (data) => {
|
||||
setSubmitStatus("idle");
|
||||
const formData = new FormData();
|
||||
formData.append("vacancyId", vacancyId);
|
||||
formData.append("vacancyTitle", vacancyTitle);
|
||||
formData.append("name", data.name);
|
||||
formData.append("email", data.email);
|
||||
if (data.phone) formData.append("phone", data.phone);
|
||||
if (data.linkedin) formData.append("linkedin", data.linkedin);
|
||||
if (data.coverLetter) formData.append("coverLetter", data.coverLetter);
|
||||
if (data.resume) formData.append("resume", data.resume, data.resume.name);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/apply", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`Submission failed: ${response.statusText}`);
|
||||
setSubmitStatus("success");
|
||||
reset();
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
setSubmitStatus("idle");
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setSubmitStatus("error");
|
||||
}
|
||||
};
|
||||
|
||||
const inputBaseStyle =
|
||||
"block w-full px-4 py-2 mt-1 text-gray-700 bg-white border border-gray-300 rounded-md focus:border-gold-500 focus:ring focus:ring-gold-500 focus:ring-opacity-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:focus:border-gold-500 font-poppins";
|
||||
const errorStyle = "border-red-500 focus:border-red-500 focus:ring-red-500";
|
||||
const errorMessageStyle = "text-red-600 text-xs mt-1";
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6 font-poppins">
|
||||
{/* Row 1: Name & Email */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label htmlFor="name" className="text-gray-700 dark:text-gray-200">
|
||||
Full Name *
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
{...register("name")}
|
||||
className={`${inputBaseStyle} ${errors.name ? errorStyle : ""}`}
|
||||
aria-invalid={errors.name ? "true" : "false"}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Email Field */}
|
||||
<div>
|
||||
<label htmlFor="email" className="text-gray-700 dark:text-gray-200">
|
||||
Email Address *
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
{...register("email")}
|
||||
className={`${inputBaseStyle} ${errors.email ? errorStyle : ""}`}
|
||||
aria-invalid={errors.email ? "true" : "false"}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{errors.email.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Phone & LinkedIn */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Phone Field */}
|
||||
<div>
|
||||
<label htmlFor="phone" className="text-gray-700 dark:text-gray-200">
|
||||
Phone Number
|
||||
</label>
|
||||
<input
|
||||
id="phone"
|
||||
type="tel"
|
||||
{...register("phone")}
|
||||
className={`${inputBaseStyle} ${errors.phone ? errorStyle : ""}`}
|
||||
aria-invalid={errors.phone ? "true" : "false"}
|
||||
/>
|
||||
{errors.phone && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{errors.phone.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* LinkedIn Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="linkedin"
|
||||
className="text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
LinkedIn Profile URL
|
||||
</label>
|
||||
<input
|
||||
id="linkedin"
|
||||
type="url"
|
||||
{...register("linkedin")}
|
||||
className={`${inputBaseStyle} ${errors.linkedin ? errorStyle : ""}`}
|
||||
placeholder="https://linkedin.com/in/yourprofile"
|
||||
aria-invalid={errors.linkedin ? "true" : "false"}
|
||||
/>
|
||||
{errors.linkedin && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{errors.linkedin.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resume/CV Upload */}
|
||||
<div>
|
||||
<label className="text-gray-700 dark:text-gray-200 block mb-2">
|
||||
Resume/CV *
|
||||
</label>
|
||||
<input
|
||||
id="resume-upload-hidden"
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
accept=".pdf,.doc,.docx,.txt"
|
||||
className="hidden"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
className={`relative flex flex-col items-center justify-center w-full p-6 transition-colors duration-300 ease-in-out border-2 border-dashed rounded-lg cursor-pointer hover:border-gold-500/80 ${
|
||||
isDragging
|
||||
? "border-gold-500 bg-gold-50"
|
||||
: errors.resume
|
||||
? "border-red-500"
|
||||
: "border-gray-300 hover:bg-gray-50"
|
||||
}`}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
onClick={() =>
|
||||
document.getElementById("resume-upload-hidden")?.click()
|
||||
}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ")
|
||||
document.getElementById("resume-upload-hidden")?.click();
|
||||
}}
|
||||
aria-label="Upload Resume/CV"
|
||||
>
|
||||
<FaUpload
|
||||
className={`w-8 h-8 mb-3 ${
|
||||
isDragging ? "text-gold-600" : "text-gray-400"
|
||||
}`}
|
||||
style={{ color: isDragging ? "#c8a93d" : undefined }}
|
||||
/>
|
||||
<p
|
||||
className={`text-sm font-semibold text-center ${
|
||||
isDragging ? "text-gold-700" : "text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{isDragging ? "Drop file here" : "Drag & drop your file here"}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
or click to browse
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
(PDF, DOC, DOCX, TXT - Max 5MB)
|
||||
</p>
|
||||
</div>
|
||||
{errors.resume && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{(errors.resume as unknown as { message: string }).message}
|
||||
</p>
|
||||
)}
|
||||
{resumeFile && (
|
||||
<div className="mt-3 flex items-center justify-between px-3 py-2 text-sm text-gray-700 bg-gray-100 border border-gray-200 rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaFileAlt className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium truncate max-w-[200px] sm:max-w-xs">
|
||||
{resumeFile.name}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
({(resumeFile.size / 1024).toFixed(1)} KB)
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeFile}
|
||||
className="text-red-500 hover:text-red-700 focus:outline-none"
|
||||
aria-label="Remove file"
|
||||
>
|
||||
<FaTimes className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Cover Letter Field */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="coverLetter"
|
||||
className="text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
Cover Letter (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="coverLetter"
|
||||
rows={4}
|
||||
{...register("coverLetter")} // Register with RHF
|
||||
className={`${inputBaseStyle} resize-vertical ${
|
||||
errors.coverLetter ? errorStyle : ""
|
||||
}`}
|
||||
aria-invalid={errors.coverLetter ? "true" : "false"}
|
||||
></textarea>
|
||||
{errors.coverLetter && (
|
||||
<p role="alert" className={errorMessageStyle}>
|
||||
{errors.coverLetter.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Button & Status */}
|
||||
<div className="flex flex-col items-center space-y-3 pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting || submitStatus === "success"} // Disable while submitting or on success
|
||||
>
|
||||
{isSubmitting ? "Submitting..." : "Submit Application"}
|
||||
</Button>
|
||||
|
||||
{submitStatus === "success" && (
|
||||
<p className="text-sm text-green-600">
|
||||
Application submitted successfully! Closing form...
|
||||
</p>
|
||||
)}
|
||||
{submitStatus === "error" && (
|
||||
<p className="text-sm text-red-600">
|
||||
Submission failed. Please check your details and try again.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
href?: string;
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive"; // Added more variants
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive" | "black";
|
||||
size?: "sm" | "md" | "lg";
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
@ -20,17 +20,21 @@ const Button: React.FC<ButtonProps> = ({
|
||||
}) => {
|
||||
// Base styles including focus ring using semantic vars
|
||||
const baseStyle =
|
||||
"inline-flex items-center justify-center rounded-lg font-semibold transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
||||
"inline-flex items-center justify-center rounded-lg font-semibold transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background cursor-pointer disabled:pointer-events-none disabled:opacity-50 text-sm";
|
||||
|
||||
// Variant styles using semantic vars (Tailwind classes generated via @theme)
|
||||
const variantStyles = {
|
||||
primary: "bg-primary text-primary-foreground hover:bg-primary/90", // bg-primary generated from --primary
|
||||
//primary: "bg-primary text-primary-foreground hover:bg-primary/90", // bg-primary generated from --primary
|
||||
primary: "bg-[linear-gradient(to_right,#e6cd4b,#fff8b3,#e0b843)] text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
outline:
|
||||
"border border-border bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
//linear-gradient(to right, #ead566, #e0be45) created using Tailwind's gradient utilities
|
||||
//i want that variant
|
||||
black:"inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 dark:bg-gray-900 dark:hover:bg-black transition-colors duration-300 font-poppins",
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
|
||||
19
components/ui/CustomButton.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
const CustomButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
className={`inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 ${className}`}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
CustomButton.displayName = "CustomButton";
|
||||
|
||||
export { CustomButton };
|
||||
19
components/ui/CustomInput.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
type CustomInputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
const CustomInput = React.forwardRef<HTMLInputElement, CustomInputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={`flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
CustomInput.displayName = "CustomInput";
|
||||
|
||||
export { CustomInput };
|
||||
18
components/ui/CustomLabel.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
type CustomLabelProps = React.LabelHTMLAttributes<HTMLLabelElement>;
|
||||
|
||||
const CustomLabel = React.forwardRef<HTMLLabelElement, CustomLabelProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<label
|
||||
ref={ref}
|
||||
className={`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
CustomLabel.displayName = "CustomLabel";
|
||||
|
||||
export { CustomLabel };
|
||||
19
components/ui/CustomTextarea.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
type CustomTextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
|
||||
const CustomTextarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
CustomTextareaProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={`flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CustomTextarea.displayName = "CustomTextarea";
|
||||
|
||||
export { CustomTextarea };
|
||||
126
components/ui/Modal.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
// src/components/ui/Modal.tsx
|
||||
import React, { ReactNode, useEffect } from "react";
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl"; // Optional size control
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
size = "2xl", // Default size
|
||||
}) => {
|
||||
// Close modal on Escape key press
|
||||
useEffect(() => {
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
if (isOpen) {
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
}
|
||||
// Cleanup listener on component unmount or when modal closes
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// Prevent scrolling on the body when the modal is open
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "auto";
|
||||
}
|
||||
// Cleanup overflow style on component unmount
|
||||
return () => {
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// Map size prop to Tailwind max-width classes
|
||||
const sizeClasses = {
|
||||
sm: "max-w-sm",
|
||||
md: "max-w-md",
|
||||
lg: "max-w-lg",
|
||||
xl: "max-w-xl",
|
||||
"2xl": "max-w-2xl",
|
||||
"3xl": "max-w-3xl",
|
||||
"4xl": "max-w-4xl",
|
||||
"5xl": "max-w-5xl",
|
||||
};
|
||||
|
||||
return (
|
||||
// Backdrop
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 backdrop-blur-sm p-4 transition-opacity duration-300 ease-in-out"
|
||||
onClick={onClose} // Close when clicking the backdrop
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={title ? "modal-title" : undefined}
|
||||
>
|
||||
{/* Modal Panel */}
|
||||
<div
|
||||
className={`relative w-full ${sizeClasses[size]} bg-white dark:bg-gray-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 ease-in-out transform scale-95 opacity-0 animate-modal-appear`}
|
||||
onClick={(e) => e.stopPropagation()} // Prevent clicks inside the modal from closing it
|
||||
>
|
||||
{/* Modal Header (Optional) */}
|
||||
{title && (
|
||||
<div className="flex items-center justify-between p-4 sm:p-5 border-b border-gray-200 dark:border-gray-700">
|
||||
{title && (
|
||||
<h2
|
||||
id="modal-title"
|
||||
className="text-lg font-semibold text-gray-900 dark:text-white font-poppins"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<FaTimes className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal Body */}
|
||||
<div className="p-5 sm:p-6 space-y-4 max-h-[80vh] overflow-y-auto">
|
||||
{" "}
|
||||
{/* Added max-height and scroll */}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add animation keyframes to your global CSS (e.g., src/app/globals.css) */}
|
||||
<style jsx global>{`
|
||||
@keyframes modal-appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.animate-modal-appear {
|
||||
animation: modal-appear 0.3s ease-out forwards;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
9
constants/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const COLORS = {
|
||||
primary: "#e1c44a",
|
||||
secondary: "#f50057",
|
||||
accent: "#ff5722",
|
||||
success: "#4caf50",
|
||||
warning: "#ff9800",
|
||||
error: "#f44336",
|
||||
info: "#2196f3",
|
||||
};
|
||||
163
lib/demo-data/vacancies.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { Vacancy } from "@/types";
|
||||
|
||||
export const demoVacancies: Vacancy[] = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Senior Frontend Engineer",
|
||||
slug: "senior-frontend-engineer",
|
||||
description:
|
||||
"We are looking for a talented Senior Frontend Engineer to join our growing team. You will be responsible for building and maintaining our user-facing web applications, ensuring high performance and responsiveness.",
|
||||
department: "Engineering",
|
||||
location: {
|
||||
city: "Cape Town",
|
||||
country: "South Africa",
|
||||
remote: false,
|
||||
},
|
||||
employmentType: "Full-time",
|
||||
experienceLevel: "Senior-level",
|
||||
salary: {
|
||||
min: 70000,
|
||||
max: 90000,
|
||||
currency: "ZAR",
|
||||
period: "Annual",
|
||||
},
|
||||
responsibilities: [
|
||||
"Develop new user-facing features using React and TypeScript.",
|
||||
"Build reusable code and libraries for future use.",
|
||||
"Ensure the technical feasibility of UI/UX designs.",
|
||||
"Optimize application for maximum speed and scalability.",
|
||||
"Collaborate with other team members and stakeholders.",
|
||||
"Mentor junior engineers.",
|
||||
],
|
||||
requiredQualifications: [
|
||||
"5+ years of experience in frontend development.",
|
||||
"Proficient understanding of web markup, including HTML5, CSS3.",
|
||||
"Strong proficiency in JavaScript, including DOM manipulation and the JavaScript object model.",
|
||||
"Thorough understanding of React.js and its core principles.",
|
||||
"Experience with popular React.js workflows (such as Flux or Redux).",
|
||||
"Familiarity with newer specifications of EcmaScript.",
|
||||
"Experience with data structure libraries (e.g., Immutable.js).",
|
||||
"Knowledge of modern authorization mechanisms, such as JSON Web Token.",
|
||||
"Familiarity with modern front-end build pipelines and tools.",
|
||||
"Experience with common front-end development tools such as Babel, Webpack, NPM, etc.",
|
||||
"Ability to understand business requirements and translate them into technical requirements.",
|
||||
"A knack for benchmarking and optimization.",
|
||||
"Familiarity with code versioning tools, such as Git.",
|
||||
],
|
||||
preferredQualifications: [
|
||||
"Experience with Next.js.",
|
||||
"Experience with TypeScript.",
|
||||
"Experience with testing frameworks like Jest or React Testing Library.",
|
||||
"Familiarity with GraphQL.",
|
||||
"Experience working in an Agile/Scrum development process.",
|
||||
],
|
||||
benefits: [
|
||||
"Competitive salary and benefits package.",
|
||||
"Opportunity to work on challenging projects.",
|
||||
"Collaborative and supportive work environment.",
|
||||
"Generous vacation policy.",
|
||||
"Professional development opportunities.",
|
||||
],
|
||||
applicationDeadline: "2025-06-15T23:59:59Z",
|
||||
postedDate: "2025-04-20T09:00:00Z",
|
||||
contactPerson: {
|
||||
name: "Jane Doe",
|
||||
email: "jane.doe@example.com",
|
||||
},
|
||||
status: "Open",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Product Marketing Manager",
|
||||
slug: "product-marketing-manager",
|
||||
description:
|
||||
"Seeking an experienced Product Marketing Manager to lead the go-to-market strategy for our innovative products. You will craft compelling messaging and positioning, drive demand, and enable sales.",
|
||||
department: "Marketing",
|
||||
location: {
|
||||
city: "Pretoria",
|
||||
country: "South Africa",
|
||||
remote: true,
|
||||
},
|
||||
employmentType: "Full-time",
|
||||
experienceLevel: "Mid-level",
|
||||
salary: {
|
||||
min: 60000,
|
||||
max: 80000,
|
||||
currency: "ZAR",
|
||||
period: "Annual",
|
||||
},
|
||||
responsibilities: [
|
||||
"Develop product positioning and messaging that differentiates our products in the market.",
|
||||
"Sales enablement – communicate the value proposition of the products to the sales team and develop the sales tools that support the selling process.",
|
||||
"Product launch – plan the launch of new products and releases and manage the cross-functional implementation of the plan.",
|
||||
"Market intelligence – be the expert on buyers, how they buy and their buying criteria; be the expert on competition.",
|
||||
"Demand generation – develop the strategy and manage the marketing programs that drive demand for your products.",
|
||||
],
|
||||
requiredQualifications: [
|
||||
"3+ years of product marketing experience with B2B SaaS products.",
|
||||
"Proven track record of successfully launching and marketing products.",
|
||||
"Excellent written and verbal communication skills.",
|
||||
"Strong analytical and project management skills.",
|
||||
"Bachelor’s degree in Marketing, Business, or related field.",
|
||||
],
|
||||
preferredQualifications: [
|
||||
"Experience in the tech industry.",
|
||||
"Familiarity with marketing automation tools (e.g., HubSpot, Marketo).",
|
||||
"MBA is a plus.",
|
||||
],
|
||||
benefits: [
|
||||
"Competitive compensation.",
|
||||
"Health, dental, and vision insurance.",
|
||||
"Flexible working hours and remote work options.",
|
||||
"Stock options.",
|
||||
],
|
||||
postedDate: "2025-04-25T10:00:00Z",
|
||||
status: "Open",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Backend Developer (Node.js)",
|
||||
slug: "backend-developer-nodejs",
|
||||
description:
|
||||
"Join our backend team to design, develop, and maintain server-side logic, databases, and APIs for our platform. Focus on scalability, performance, and reliability.",
|
||||
department: "Engineering",
|
||||
location: {
|
||||
city: "Johannesburg",
|
||||
country: "South Africa",
|
||||
remote: false,
|
||||
},
|
||||
employmentType: "Full-time",
|
||||
experienceLevel: "Mid-level",
|
||||
responsibilities: [
|
||||
"Design and implement low-latency, high-availability, and performant applications.",
|
||||
"Write reusable, testable, and efficient code.",
|
||||
"Integration of user-facing elements developed by front-end developers with server side logic.",
|
||||
"Implementation of security and data protection.",
|
||||
"Integration of data storage solutions (may include databases, key-value stores, blob stores, etc.).",
|
||||
],
|
||||
requiredQualifications: [
|
||||
"3+ years of experience as a Backend Developer.",
|
||||
"Strong proficiency with Node.js and JavaScript/TypeScript.",
|
||||
"Experience with frameworks such as Express, Koa, or NestJS.",
|
||||
"Understanding the nature of asynchronous programming and its quirks and workarounds.",
|
||||
"Experience with RESTful APIs design and implementation.",
|
||||
"Knowledge of database technologies (SQL and NoSQL).",
|
||||
"Understanding fundamental design principles behind a scalable application.",
|
||||
"Proficient understanding of code versioning tools, such as Git.",
|
||||
],
|
||||
preferredQualifications: [
|
||||
"Experience with microservices architecture.",
|
||||
"Familiarity with Docker and Kubernetes.",
|
||||
"Experience with cloud platforms like AWS, Azure, or GCP.",
|
||||
"Knowledge of message queue systems (e.g., RabbitMQ, Kafka).",
|
||||
],
|
||||
benefits: [
|
||||
"Opportunity to work with modern technologies.",
|
||||
"Dynamic and international team.",
|
||||
"Regular team events.",
|
||||
"Public transport ticket.",
|
||||
],
|
||||
postedDate: "2025-04-27T08:30:00Z",
|
||||
status: "Open",
|
||||
},
|
||||
];
|
||||
5
lib/directus.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { createDirectus, rest } from "@directus/sdk";
|
||||
|
||||
export const directus = createDirectus(
|
||||
String(process.env.DIRECTUS_API_ENDPOINT!)
|
||||
).with(rest());
|
||||
13
lib/helpers.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const formatDate = (dateString?: string): string => {
|
||||
if (!dateString) return "N/A";
|
||||
try {
|
||||
return new Date(dateString).toLocaleDateString("en-ZA", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Invalid date format:", dateString, e);
|
||||
return "Invalid Date";
|
||||
}
|
||||
};
|
||||
8
lib/query/home.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { readItems } from "@directus/sdk";
|
||||
import { directus } from "../directus";
|
||||
import { HeroSection } from "@/types";
|
||||
|
||||
export async function getHome() {
|
||||
// Assuming '1' is the primary key for the singleton "home" item
|
||||
return directus.request(readItems("home")) as unknown as HeroSection;
|
||||
}
|
||||
66
lib/query/post.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { directus } from "@/lib/directus";
|
||||
import { ItemsQuery, Post } from "@/types";
|
||||
import { readItem, readItems } from "@directus/sdk";
|
||||
|
||||
// Construct base URL for assets from environment variable
|
||||
const assetsUrl = `${process.env.DIRECTUS_API_ENDPOINT}/assets/`;
|
||||
|
||||
function getFullImageUrl(imageId: string | null | undefined): string | null {
|
||||
if (!imageId || !assetsUrl) {
|
||||
return null;
|
||||
}
|
||||
return `${assetsUrl}${imageId}`;
|
||||
}
|
||||
|
||||
// Function to fetch all published posts
|
||||
export async function getPosts(): Promise<Post[]> {
|
||||
try {
|
||||
const postsData = await directus.request(
|
||||
readItems("posts", {
|
||||
fields: [
|
||||
"slug",
|
||||
"title",
|
||||
"excerpt",
|
||||
"featured_image",
|
||||
"date_created",
|
||||
"content",
|
||||
],
|
||||
filter: {
|
||||
status: { _eq: "published" },
|
||||
},
|
||||
sort: ["-date_created"],
|
||||
})
|
||||
);
|
||||
|
||||
const posts = postsData.map((post) => ({
|
||||
...post,
|
||||
featured_image: getFullImageUrl(post.featured_image),
|
||||
})) as Post[];
|
||||
|
||||
return posts;
|
||||
} catch (error) {
|
||||
console.error("Error fetching posts:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Function to fetch a single post by slug
|
||||
export async function getPostBySlug(
|
||||
slug: string,
|
||||
options?: ItemsQuery
|
||||
): Promise<Post | null> {
|
||||
try {
|
||||
const postData = await directus.request(readItem("posts", slug, options));
|
||||
|
||||
// Map data to include full image URL
|
||||
const post = {
|
||||
...postData,
|
||||
featured_image: getFullImageUrl(postData.featured_image),
|
||||
} as Post; // Adjust cast if needed
|
||||
|
||||
return post;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching post with slug ${slug}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,10 @@ const nextConfig: NextConfig = {
|
||||
protocol: "https",
|
||||
hostname: "storage.cvevolve.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: process.env.DIRECTUS_API_ENDPOINT!.replace("https://", ""),
|
||||
},
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
|
||||
7
oms-website.code-workspace
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
60
package-lock.json
generated
@ -9,8 +9,11 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.9.0",
|
||||
"@directus/sdk": "^18.0.3",
|
||||
"@google/generative-ai": "^0.24.0",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"minio": "^8.0.5",
|
||||
"next": "15.3.1",
|
||||
"next-auth": "^5.0.0-beta.26",
|
||||
@ -18,6 +21,7 @@
|
||||
"nodemailer": "^6.10.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-spinners": "^0.16.1",
|
||||
"react-toggle-dark-mode": "^1.1.1",
|
||||
@ -646,6 +650,18 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@directus/sdk": {
|
||||
"version": "18.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-18.0.3.tgz",
|
||||
"integrity": "sha512-PnEDRDqr2x/DG3HZ3qxU7nFp2nW6zqJqswjii57NhriXgTz4TBUI8NmSdzQvnyHuTL9J0nedYfQGfW4v8odS1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/directus/directus?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
||||
@ -1251,6 +1267,18 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
|
||||
"integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/utils": "^0.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-hook-form": "^7.55.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@ -2576,6 +2604,12 @@
|
||||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/utils": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@ -4604,6 +4638,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
@ -8821,6 +8865,22 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.56.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.1.tgz",
|
||||
"integrity": "sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/react-hook-form"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
|
||||
@ -10,8 +10,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.9.0",
|
||||
"@directus/sdk": "^18.0.3",
|
||||
"@google/generative-ai": "^0.24.0",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"minio": "^8.0.5",
|
||||
"next": "15.3.1",
|
||||
"next-auth": "^5.0.0-beta.26",
|
||||
@ -19,6 +22,7 @@
|
||||
"nodemailer": "^6.10.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-spinners": "^0.16.1",
|
||||
"react-toggle-dark-mode": "^1.1.1",
|
||||
|
||||
BIN
public/About us-min.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/Privacy-Policy/Data-Privacy-Statement-for-Candidates.pdf
Normal file
BIN
public/Privacy-Policy/Recruitment-Privacy-Policy.pdf
Normal file
BIN
public/about-2.jpg
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/banner-1.jpeg
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
public/custom-software.jpg
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
public/custome-software-2.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/custome-software-3.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
2979
public/custome-software-3.svg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/hero-2.jpg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
public/images/absa.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/images/bcx.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/images/dashboard.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
public/images/jacaranda.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
9
public/images/meeting.svg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
16
public/images/obse.svg
Normal file
|
After Width: | Height: | Size: 731 KiB |
BIN
public/images/salesforce.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
public/images/sasol.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
13
public/images/sybrin.svg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/images/team-collaborative-2.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
7684
public/images/team-collaborative-2.svg
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
9
public/images/team.svg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/images/toyota-logo.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
public/images/toyota.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
117
types/index.d.ts
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
export interface OutputData {
|
||||
time: number;
|
||||
blocks: ContentBlock[];
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface ContentBlock {
|
||||
id: string;
|
||||
type: string;
|
||||
data:
|
||||
| ParagraphData
|
||||
| HeaderData
|
||||
| ListData
|
||||
| ImageData
|
||||
| QuoteData
|
||||
| CodeData;
|
||||
}
|
||||
|
||||
export interface ParagraphData {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface HeaderData {
|
||||
text: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface ListData {
|
||||
style: "ordered" | "unordered";
|
||||
items: string[];
|
||||
}
|
||||
|
||||
export interface ImageData {
|
||||
file: {
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
export interface QuoteData {
|
||||
text: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
export interface CodeData {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
slug: string;
|
||||
status: string;
|
||||
user_created: string;
|
||||
date_created: string;
|
||||
user_updated: string | null;
|
||||
date_updated: string | null;
|
||||
title: string;
|
||||
content: OutputData | null;
|
||||
excerpt: string | null;
|
||||
featured_image: string | null;
|
||||
imageUrl?: string | null;
|
||||
}
|
||||
export interface HeroSection {
|
||||
id: string;
|
||||
hero_title?: string;
|
||||
hero_subtitle?: string;
|
||||
hero_cover?: string;
|
||||
hero_buttons?: HeroButton[];
|
||||
}
|
||||
|
||||
interface HeroButton {
|
||||
label?: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
interface ItemsQuery {
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
export interface Vacancy {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
department: string;
|
||||
location: {
|
||||
city: string;
|
||||
country: string;
|
||||
remote: boolean;
|
||||
};
|
||||
employmentType: "Full-time" | "Part-time" | "Contract" | "Internship";
|
||||
experienceLevel:
|
||||
| "Entry-level"
|
||||
| "Mid-level"
|
||||
| "Senior-level"
|
||||
| "Lead"
|
||||
| "Manager";
|
||||
salary?: {
|
||||
min: number;
|
||||
max: number;
|
||||
currency: string;
|
||||
period: "Annual" | "Monthly" | "Hourly";
|
||||
};
|
||||
responsibilities: string[];
|
||||
requiredQualifications: string[];
|
||||
preferredQualifications?: string[];
|
||||
benefits?: string[];
|
||||
applicationDeadline?: string; // ISO 8601 date string
|
||||
postedDate: string; // ISO 8601 date string
|
||||
contactPerson?: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
};
|
||||
status: "Open" | "Closed" | "Filled";
|
||||
}
|
||||