This commit is contained in:
Iliyan Angelov
2025-10-13 01:49:06 +03:00
parent 76c857b4f5
commit 5ad9cbe3a6
97 changed files with 5752 additions and 2376 deletions

View File

@@ -131,10 +131,6 @@ export default function RootLayout({
e.preventDefault();
return false;
});
// Console warning
console.log('%cSTOP!', 'color: red; font-size: 40px; font-weight: bold;');
console.log('%c© GNX Soft - All Rights Reserved', 'font-size: 14px;');
});
})();
`,
@@ -150,8 +146,8 @@ export default function RootLayout({
<CookieConsentProvider
config={{
companyName: "GNX Soft",
privacyPolicyUrl: "/policy",
cookiePolicyUrl: "/policy",
privacyPolicyUrl: "/policy?type=privacy",
cookiePolicyUrl: "/policy?type=privacy",
dataControllerEmail: "privacy@gnxsoft.com",
retentionPeriod: 365,
enableAuditLog: true,

View File

@@ -27,7 +27,6 @@ export async function generateStaticParams() {
slug: service.slug,
}));
} catch (error) {
console.error('Error generating static params:', error);
return [];
}
}
@@ -54,7 +53,6 @@ const ServicePage = async ({ params }: ServicePageProps) => {
const { slug } = await params;
service = await serviceService.getServiceBySlug(slug);
} catch (error) {
console.error('Error fetching service:', error);
notFound();
}

View File

@@ -112,7 +112,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
}
// Fetch dynamic career postings
const careerResponse = await fetch(`${apiUrl}/career/positions/`, {
const careerResponse = await fetch(`${apiUrl}/career/jobs`, {
next: { revalidate: 3600 },
});
@@ -129,7 +129,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
return [...staticPages, ...servicePages, ...blogPages, ...caseStudyPages, ...careerPages];
} catch (error) {
console.error('Error generating sitemap:', error);
// Return at least static pages if API fails
return staticPages;
}

View File

@@ -2,7 +2,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import JobPositionViewSet, JobApplicationViewSet
router = DefaultRouter()
router = DefaultRouter(trailing_slash=False)
router.register(r'jobs', JobPositionViewSet, basename='job')
router.register(r'applications', JobApplicationViewSet, basename='application')

View File

@@ -2,6 +2,7 @@ from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from django_filters.rest_framework import DjangoFilterBackend
from django.shortcuts import get_object_or_404
import logging
@@ -63,6 +64,7 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
queryset = JobApplication.objects.all()
serializer_class = JobApplicationSerializer
parser_classes = [MultiPartParser, FormParser, JSONParser]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['job', 'status']
search_fields = ['first_name', 'last_name', 'email', 'job__title']
@@ -85,20 +87,51 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
"""Submit a job application"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
# Build data dict - Django QueryDict returns lists, so we need to get first item
data = {}
# Get POST data (text fields)
for key in request.POST.keys():
value = request.POST.get(key)
data[key] = value
# Get FILES data
for key in request.FILES.keys():
file_obj = request.FILES.get(key)
data[key] = file_obj
except Exception as e:
logger.error(f"Error parsing request: {str(e)}")
return Response(
{'error': 'Error parsing request data'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=data)
if not serializer.is_valid():
logger.error(f"Validation errors: {serializer.errors}")
return Response(
{'error': 'Validation failed', 'details': serializer.errors},
status=status.HTTP_400_BAD_REQUEST
)
try:
# Save the application
application = serializer.save()
# Send email notifications
email_service = CareerEmailService()
email_service.send_application_confirmation(application)
email_service.send_application_notification_to_admin(application)
logger.info(f"New job application received: {application.full_name} for {application.job.title}")
# Try to send email notifications (non-blocking - don't fail if emails fail)
try:
email_service = CareerEmailService()
email_service.send_application_confirmation(application)
email_service.send_application_notification_to_admin(application)
logger.info(f"Email notifications sent successfully for application {application.id}")
except Exception as email_error:
# Log email error but don't fail the application submission
logger.warning(f"Failed to send email notifications for application {application.id}: {str(email_error)}")
return Response(
{
'message': 'Application submitted successfully',
@@ -106,6 +139,7 @@ class JobApplicationViewSet(viewsets.ModelViewSet):
},
status=status.HTTP_201_CREATED
)
except Exception as e:
logger.error(f"Error submitting job application: {str(e)}", exc_info=True)
return Response(

Binary file not shown.

View File

@@ -245,26 +245,35 @@ CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = DEBUG # Only allow all origins in development
# Django URL configuration
APPEND_SLASH = True
# Email Configuration
# Production email settings - use SMTP backend
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='mail.gnxsoft.com')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')
# Always use .env configuration for consistency across dev and production
# Set USE_SMTP_IN_DEV=True in your .env file to test real email sending in development
USE_SMTP_IN_DEV = config('USE_SMTP_IN_DEV', default=False, cast=bool)
# Email addresses - same for dev and production (from .env)
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='support@gnxsoft.com')
COMPANY_EMAIL = config('COMPANY_EMAIL', default='support@gnxsoft.com')
SUPPORT_EMAIL = config('SUPPORT_EMAIL', default=COMPANY_EMAIL)
if DEBUG and not USE_SMTP_IN_DEV:
# Development: Use console backend to print emails to console (no SMTP required)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
else:
# Production or Dev with SMTP enabled - use SMTP backend
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='mail.gnxsoft.com')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
# Email timeout settings for production
EMAIL_TIMEOUT = config('EMAIL_TIMEOUT', default=30, cast=int)
# Company email for contact form notifications
COMPANY_EMAIL = config('COMPANY_EMAIL')
# Support email for ticket notifications
SUPPORT_EMAIL = config('SUPPORT_EMAIL', default=config('COMPANY_EMAIL'))
# Site URL for email links
SITE_URL = config('SITE_URL', default='http://localhost:3000')

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
test resume content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Test resume content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
Fake PDF content

View File

@@ -0,0 +1 @@
test resume

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

View File

@@ -14,6 +14,7 @@ class PolicyAdmin(admin.ModelAdmin):
list_filter = ['type', 'is_active', 'last_updated']
search_fields = ['title', 'type', 'description']
prepopulated_fields = {'slug': ('type',)}
readonly_fields = ('last_updated',)
inlines = [PolicySectionInline]
fieldsets = (

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2025-10-11 12:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('services', '0006_service_deliverables_description_and_more'),
]
operations = [
migrations.AddField(
model_name='service',
name='image2',
field=models.ImageField(blank=True, help_text='Secondary service image', null=True, upload_to='services/images/'),
),
migrations.AddField(
model_name='service',
name='image2_url',
field=models.CharField(blank=True, help_text='External secondary image URL (alternative to uploaded image)', max_length=500),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 4.2.7 on 2025-10-11 12:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0007_service_image2_service_image2_url'),
]
operations = [
migrations.RemoveField(
model_name='service',
name='image2',
),
migrations.RemoveField(
model_name='service',
name='image2_url',
),
]

View File

@@ -89,23 +89,28 @@ class SupportTicketCreateSerializer(serializers.ModelSerializer):
def validate_user_email(self, value):
"""
Validate that the email is registered and active in the RegisteredEmail model
Validate email format and optionally check if registered
"""
from .models import RegisteredEmail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError as DjangoValidationError
# Check if email exists and is active in RegisteredEmail model
# Basic email format validation
try:
validate_email(value)
except DjangoValidationError:
raise serializers.ValidationError("Please enter a valid email address.")
# Optional: Check if email is registered (for analytics/tracking)
# But don't block ticket creation if not registered
try:
registered_email = RegisteredEmail.objects.get(email=value)
if not registered_email.is_active:
raise serializers.ValidationError(
"This email has been deactivated. "
"Please contact us at support@gnxsoft.com for assistance."
)
# Log this but don't block the ticket
logger.warning(f'Ticket submitted by deactivated registered email: {value}')
except RegisteredEmail.DoesNotExist:
raise serializers.ValidationError(
"This email is not registered in our system. "
"Please contact us at support@gnxsoft.com to register your email first."
)
# This is fine - unregistered users can submit tickets
logger.info(f'Ticket submitted by unregistered email: {value}')
return value
@@ -133,12 +138,14 @@ class SupportTicketCreateSerializer(serializers.ModelSerializer):
user_name=ticket.user_name
)
# Update registered email statistics
# Update registered email statistics (only if email is registered)
try:
registered_email = RegisteredEmail.objects.get(email=validated_data['user_email'])
registered_email.increment_ticket_count()
logger.info(f'Updated ticket count for registered email: {validated_data["user_email"]}')
except RegisteredEmail.DoesNotExist:
logger.warning(f'RegisteredEmail not found for {validated_data["user_email"]} after validation')
# This is normal now - unregistered users can submit tickets
logger.info(f'Ticket created by unregistered email: {validated_data["user_email"]}')
# Send email notifications
try:

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import django
# Add the backend directory to Python path
sys.path.append('/home/gnx/Desktop/gnx-react/backend')
# Set up Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gnx.settings')
django.setup()
from about.models import AboutService
# Update the service image
service = AboutService.objects.get(id=1)
service.image = 'about/services/service-image.jpg'
service.save()
print(f"Updated service image: {service.image}")
print(f"Service image URL: {service.image.url if service.image else 'None'}")

View File

@@ -1,183 +0,0 @@
"use client";
import { useState, useRef } from "react";
import Image from "next/image";
import { useServiceManagement } from "@/lib/hooks/useServices";
interface ServiceImageUploadProps {
serviceSlug: string;
currentImageUrl?: string;
onImageUploaded?: (service: any) => void;
}
const ServiceImageUpload: React.FC<ServiceImageUploadProps> = ({
serviceSlug,
currentImageUrl,
onImageUploaded,
}) => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { uploadServiceImage, loading, error } = useServiceManagement();
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
// Validate file type
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
// Validate file size (5MB limit)
if (file.size > 5 * 1024 * 1024) {
alert('File size must be less than 5MB');
return;
}
setSelectedFile(file);
// Create preview URL
const url = URL.createObjectURL(file);
setPreviewUrl(url);
}
};
const handleUpload = async () => {
if (!selectedFile) return;
try {
setUploading(true);
const updatedService = await uploadServiceImage(serviceSlug, selectedFile);
// Clean up preview URL
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
}
setSelectedFile(null);
setPreviewUrl(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
if (onImageUploaded) {
onImageUploaded(updatedService);
}
alert('Image uploaded successfully!');
} catch (err) {
console.error('Upload failed:', err);
alert('Failed to upload image. Please try again.');
} finally {
setUploading(false);
}
};
const handleCancel = () => {
setSelectedFile(null);
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
setPreviewUrl(null);
}
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
return (
<div className="service-image-upload">
<div className="mb-3">
<label htmlFor="image-upload" className="form-label">
Service Image
</label>
<input
ref={fileInputRef}
type="file"
id="image-upload"
className="form-control"
accept="image/*"
onChange={handleFileSelect}
disabled={uploading || loading}
/>
<div className="form-text">
Select an image file (JPG, PNG, GIF). Maximum size: 5MB.
</div>
</div>
{/* Current Image */}
{currentImageUrl && !previewUrl && (
<div className="mb-3">
<label className="form-label">Current Image:</label>
<div className="current-image">
<Image
src={currentImageUrl}
alt="Current service image"
className="img-thumbnail"
width={200}
height={200}
style={{ maxWidth: '200px', maxHeight: '200px', objectFit: 'cover' }}
/>
</div>
</div>
)}
{/* Preview */}
{previewUrl && (
<div className="mb-3">
<label className="form-label">Preview:</label>
<div className="image-preview">
<Image
src={previewUrl}
alt="Preview"
className="img-thumbnail"
width={200}
height={200}
style={{ maxWidth: '200px', maxHeight: '200px', objectFit: 'cover' }}
/>
</div>
</div>
)}
{/* Error Display */}
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
{/* Action Buttons */}
{selectedFile && (
<div className="d-flex gap-2">
<button
type="button"
className="btn btn-primary"
onClick={handleUpload}
disabled={uploading || loading}
>
{uploading || loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
Uploading...
</>
) : (
'Upload Image'
)}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={handleCancel}
disabled={uploading || loading}
>
Cancel
</button>
</div>
)}
</div>
);
};
export default ServiceImageUpload;

View File

@@ -1,137 +0,0 @@
'use client';
import { useState } from 'react';
export default function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
// Simulate form submission
setTimeout(() => {
setSuccess(true);
setFormData({ name: '', email: '', subject: '', message: '' });
setLoading(false);
}, 2000);
};
if (success) {
return (
<div className="alert alert-success" role="alert">
<h4 className="alert-heading">Message Sent!</h4>
<p>Thank you for your message. We&apos;ll get back to you soon.</p>
<button
className="btn btn-outline-success"
onClick={() => setSuccess(false)}
>
Send Another Message
</button>
</div>
);
}
return (
<div className="container py-5">
<div className="row justify-content-center">
<div className="col-md-8">
<h2 className="text-center mb-4">Contact Us</h2>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-md-6 mb-3">
<label htmlFor="name" className="form-label">Name *</label>
<input
type="text"
className="form-control"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6 mb-3">
<label htmlFor="email" className="form-label">Email *</label>
<input
type="email"
className="form-control"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="subject" className="form-label">Subject</label>
<input
type="text"
className="form-control"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="message" className="form-label">Message *</label>
<textarea
className="form-control"
id="message"
name="message"
rows={5}
value={formData.message}
onChange={handleChange}
required
></textarea>
</div>
<div className="text-center">
<button
type="submit"
className="btn btn-primary btn-lg"
disabled={loading}
>
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
Sending...
</>
) : (
'Send Message'
)}
</button>
</div>
</form>
</div>
</div>
</div>
);
}

View File

@@ -1,84 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
export default function ServicesList() {
// Static services data
const services = [
{
id: 1,
title: "Web Development",
description: "Custom web applications built with modern technologies",
slug: "web-development",
icon: "code",
price: 2500,
featured: true,
display_order: 1,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 2,
title: "Mobile Apps",
description: "Native and cross-platform mobile applications",
slug: "mobile-apps",
icon: "phone",
price: 3500,
featured: false,
display_order: 2,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
];
const handleDeleteService = (id: number) => {
if (!confirm('Are you sure you want to delete this service?')) {
return;
}
// Note: This is a demo - in a real app, you'd handle deletion differently
console.log('Service deletion requested for ID:', id);
};
return (
<div className="container py-5">
<div className="row">
<div className="col-12">
<h2 className="mb-4">Our Services</h2>
<div className="row">
{services.map((service) => (
<div key={service.id} className="col-md-6 col-lg-4 mb-4">
<div className="card h-100">
<div className="card-body">
<div className="d-flex justify-content-between align-items-start mb-3">
<h5 className="card-title">{service.title}</h5>
{service.featured && (
<span className="badge bg-primary">Featured</span>
)}
</div>
<p className="card-text">{service.description}</p>
<div className="mt-auto">
<a
href={`/services/${service.slug}`}
className="btn btn-primary me-2"
>
Learn More
</a>
<button
className="btn btn-outline-danger btn-sm"
onClick={() => handleDeleteService(service.id)}
>
Delete
</button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,367 +0,0 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Navigation } from "swiper/modules";
import "swiper/swiper-bundle.css";
import Counter from "../../common/Counter";
import one from "@/public/images/study/one.png";
import two from "@/public/images/study/two.png";
import three from "@/public/images/study/three.png";
import four from "@/public/images/study/four.png";
import five from "@/public/images/study/five.png";
const AboutCase = () => {
return (
<section className="tp-study pt-120 pb-120 bg-quinary">
<div className="container">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-lg-7">
<div className="tp-lp-title text-center text-lg-start">
<div className="enterprise-badge mb-20">
<span className="badge enterprise-badge-content">
<i className="fa-solid fa-chart-line"></i>
Success Stories
</span>
</div>
<h2 className="mt-8 fw-7 text-secondary title-anim">
Enterprise Success Stories
</h2>
<p className="cur-lg mt-20">
Discover how we&apos;ve helped Fortune 500 companies and innovative startups
achieve their digital transformation goals through cutting-edge technology solutions.
</p>
</div>
</div>
<div className="col-12 col-lg-5">
<div className="tp-study-arrows d-flex justify-content-center justify-content-lg-end">
<button className="prev-study" aria-label="previous study">
<span className="material-symbols-outlined">west</span>
</button>
<button className="next-study" aria-label="next study">
<span className="material-symbols-outlined">east</span>
</button>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="tp-study-slider-wrapper">
<div className="tp-study-slider-wrap">
<Swiper
slidesPerView={"auto"}
spaceBetween={24}
slidesPerGroup={1}
freeMode={true}
speed={1200}
loop={true}
roundLengths={true}
modules={[Autoplay, Navigation]}
autoplay={{
delay: 5000,
disableOnInteraction: false,
pauseOnMouseEnter: true,
}}
className="tp-study-slider"
navigation={{
nextEl: ".prev-study",
prevEl: ".next-study",
}}
>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={one}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={two}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={three}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={four}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={five}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={one}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={two}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={three}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={four}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-study-slider__single">
<div className="thumb">
<Link href="case-study-single">
<Image
src={five}
className="w-100 mh-400"
alt="Image"
width={400}
height={400}
/>
</Link>
</div>
<div className="content text-center">
<h5 className="mt-8 mb-12 text-white fw-5 text-uppercase">
<Link href="case-study-single">ENTERPRISE CRM</Link>
</h5>
<Link href="case-study-single" className="btn-angle">
View
<span></span>
</Link>
</div>
</div>
</SwiperSlide>
</Swiper>
</div>
</div>
</div>
</div>
<div className="row pt-120 vertical-column-gap-lg">
<div className="col-12 col-sm-6 col-xl-3">
<div className="counter__single text-center">
<h2 className="fw-5 text-secondary mt-8 mb-16">
<span className="odometer">
<Counter value={500} />
</span>
<span>+</span>
</h2>
<p className="text-tertiary">Enterprise Projects</p>
</div>
</div>
<div className="col-12 col-sm-6 col-xl-3">
<div className="counter__single text-center">
<h2 className="fw-5 text-secondary mt-8 mb-16">
<span className="odometer">
<Counter value={99} />
</span>
<span>.9%</span>
</h2>
<p className="text-tertiary">Uptime SLA</p>
</div>
</div>
<div className="col-12 col-sm-6 col-xl-3">
<div className="counter__single text-center">
<h2 className="fw-5 text-secondary mt-8 mb-16">
<span className="odometer">
<Counter value={15} />
</span>
<span>+</span>
</h2>
<p className="text-tertiary">Years Experience</p>
</div>
</div>
<div className="col-12 col-sm-6 col-xl-3">
<div className="counter__single text-center border-0">
<h2 className="fw-5 text-secondary mt-8 mb-16">
<span className="odometer">
<Counter value={200} />
</span>
<span>+</span>
</h2>
<p className="text-tertiary">Expert Engineers</p>
</div>
</div>
</div>
</div>
</section>
);
};
export default AboutCase;

View File

@@ -3,6 +3,7 @@ import Image from "next/image";
import Link from "next/link";
import { useAbout } from "@/lib/hooks/useAbout";
import { AboutService as AboutServiceType, AboutProcess } from "@/lib/api/aboutService";
import { getValidImageUrl, FALLBACK_IMAGES } from "@/lib/imageUtils";
import thumb from "@/public/images/service/two.png";
import thumbTwo from "@/public/images/service/three.png";
@@ -62,13 +63,23 @@ const AboutServiceComponent = () => {
<Link href="service-single" className="w-100">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={serviceData?.image_url || thumb}
className="w-100 parallax-image"
alt="Enterprise Technology Solutions"
width={400}
height={300}
/>
{serviceData?.image_url ? (
<img
src={getValidImageUrl(serviceData.image_url, FALLBACK_IMAGES.SERVICE)}
className="w-100 parallax-image"
alt="Enterprise Technology Solutions"
width={400}
height={300}
/>
) : (
<Image
src={thumb}
className="w-100 parallax-image"
alt="Enterprise Technology Solutions"
width={400}
height={300}
/>
)}
</div>
</div>
</Link>
@@ -241,13 +252,23 @@ const AboutServiceComponent = () => {
<Link href="service-single" className="w-100">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={processData?.image_url || thumbTwo}
className="w-100 parallax-image"
alt="Enterprise Development Process"
width={400}
height={300}
/>
{processData?.image_url ? (
<img
src={getValidImageUrl(processData.image_url, FALLBACK_IMAGES.SERVICE)}
className="w-100 parallax-image"
alt="Enterprise Development Process"
width={400}
height={300}
/>
) : (
<Image
src={thumbTwo}
className="w-100 parallax-image"
alt="Enterprise Development Process"
width={400}
height={300}
/>
)}
</div>
</div>
</Link>

View File

@@ -3,6 +3,7 @@ import Image from "next/image";
import Link from "next/link";
import { useAbout } from "@/lib/hooks/useAbout";
import { AboutJourney } from "@/lib/api/aboutService";
import { getValidImageUrl, FALLBACK_IMAGES } from "@/lib/imageUtils";
import thumb from "@/public/images/start-thumb.png";
const AboutStarter = () => {
@@ -52,7 +53,7 @@ const AboutStarter = () => {
const journeyData = data?.journey as AboutJourney | undefined;
return (
<section className="tp-gallery">
<section className="tp-gallery about-compact">
<div className="container">
<div className="row align-items-center">
<div className="col-12 col-lg-6">
@@ -65,7 +66,7 @@ const AboutStarter = () => {
{journeyData?.title || "From Startup to Enterprise Leader"}
</h2>
<p>
{journeyData?.description || "Founded in 2008 by three visionary engineers, Itify Technologies began as a small startup with a big dream: to revolutionize how enterprises approach software development. What started as a passion project has grown into a global enterprise software company serving Fortune 500 clients worldwide."}
{journeyData?.description || "Founded in 2020 by three visionary engineers, GNX Technologies began as a small startup with a big dream: to revolutionize how enterprises approach software development. What started as a passion project has grown into a global enterprise software company serving Fortune 500 clients worldwide."}
</p>
<div className="journey-milestones">
{journeyData?.milestones && journeyData.milestones.length > 0 ? (
@@ -112,7 +113,7 @@ const AboutStarter = () => {
</>
)}
</div>
<div className="mt-4">
<div className="cta-wrap">
<Link
href={journeyData?.cta_link || "services"}
className="btn-line"
@@ -124,26 +125,123 @@ const AboutStarter = () => {
</div>
<div className="col-12 col-lg-6">
<div className="tp-gallery__thumb">
<Image
src={journeyData?.image_url || thumb}
className="w-100"
alt="Enterprise Journey"
width={500}
height={400}
style={{
objectFit: 'contain',
borderRadius: '12px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.1)',
maxWidth: '100%',
maxHeight: '500px',
width: '100%',
height: 'auto'
}}
/>
{journeyData?.image_url ? (
<img
src={getValidImageUrl(journeyData.image_url, FALLBACK_IMAGES.DEFAULT)}
className="w-100 compact-img"
alt="Enterprise Journey"
width={500}
height={400}
/>
) : (
<Image
src={thumb}
className="w-100 compact-img"
alt="Enterprise Journey"
width={500}
height={400}
/>
)}
</div>
</div>
</div>
</div>
<style jsx>{`
.about-compact {
padding: 32px 0;
}
/* Typography */
.about-compact .enterprise-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
line-height: 1;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
color: #fff;
margin-bottom: 10px;
}
.about-compact .enterprise-badge i { font-size: 12px; }
.about-compact .title-anim {
font-size: clamp(20px, 2.2vw, 28px);
line-height: 1.25;
margin: 0 0 8px 0;
}
.about-compact p {
font-size: 14px;
line-height: 1.55;
margin: 0 0 12px 0;
color: rgba(255,255,255,0.85);
}
/* Milestones */
.about-compact .journey-milestones {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
@media (min-width: 992px) {
.about-compact .journey-milestones { grid-template-columns: 1fr 1fr; }
}
.about-compact .milestone-item {
display: grid;
grid-template-columns: auto 1fr;
align-items: start;
gap: 10px;
padding: 10px 12px;
border: 1px solid rgba(255,255,255,0.08);
background: rgba(255,255,255,0.03);
border-radius: 10px;
}
.about-compact .year-badge {
display: inline-block;
padding: 4px 8px;
font-size: 12px;
line-height: 1;
border-radius: 6px;
background: rgba(255,255,255,0.08);
color: #fff;
white-space: nowrap;
}
.about-compact .milestone-content h6 {
margin: 0 0 4px 0;
font-size: 14px;
line-height: 1.3;
}
.about-compact .milestone-content p {
margin: 0;
font-size: 13px;
color: rgba(255,255,255,0.75);
}
/* CTA */
.about-compact .cta-wrap { margin-top: 12px; }
.about-compact :global(.btn-line) {
padding: 8px 14px;
font-size: 13px;
line-height: 1;
}
/* Image */
.about-compact .tp-gallery__thumb { margin-top: 8px; }
@media (min-width: 992px) {
.about-compact .tp-gallery__thumb { margin-top: 0; }
}
.about-compact :global(.compact-img),
.about-compact .compact-img {
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
max-height: 260px;
width: 100%;
height: auto;
object-fit: contain;
}
`}</style>
</section>
);
};

View File

@@ -31,7 +31,6 @@ const PostFilterButtons = ({ handleClick, active }: any) => {
}
if (error) {
console.error('Error loading categories:', error);
// Fallback to showing "All" button only
return (
<div className="row">

View File

@@ -61,7 +61,6 @@ const PostFilterItems = ({ currentPage, onPageChange, onTotalPagesChange, postsP
}
if (error) {
console.error('Error loading posts:', error);
return (
<>
<PostFilterButtons active={active} handleClick={handleCategoryClick} />

File diff suppressed because it is too large Load Diff

View File

@@ -91,11 +91,11 @@ const JobSingle = ({ job }: JobSingleProps) => {
<div className="container">
<div className="row">
<div className="col-12">
<div className="job-header-content" style={{ color: 'white' }}>
<div className="job-header-content" style={{ color: '#ffffff' }}>
<div className="mb-12 mb-md-16">
<span className="badge" style={{
backgroundColor: 'rgba(255,255,255,0.2)',
color: 'white',
color: '#ffffff',
padding: '6px 12px',
borderRadius: '20px',
fontSize: '12px',
@@ -110,7 +110,7 @@ const JobSingle = ({ job }: JobSingleProps) => {
<h1 className="fw-7 mb-16 mb-md-20 mb-lg-24" style={{
fontSize: 'clamp(1.75rem, 5vw, 3.5rem)',
lineHeight: '1.2',
color: 'white'
color: '#ffffff'
}}>
{job.title}
</h1>

View File

@@ -44,13 +44,13 @@ const CaseItems = () => {
<h2 className="mt-8 title-anim fw-7 text-secondary mb-24">
Case Studies
</h2>
<p className="cur-lg">
Lorem ipsum dolor sit amet consectetur. Morbi in non nibh
netus tortor. Non vitae ut consectetur sit quam egestas
praesent. Enim augue cras donec sed pellentesque tortor
maecenas. Tellus duis sit justo neque. Est elit diam quam
venenatis sit morbi sed dignissim eros.
</p>
<p className="cur-lg">
Discover how we help enterprises solve complex challenges with
secure, scalable solutions. Our case studies highlight real
business outcomes accelerated delivery, reduced costs,
improved reliability, and data-driven growth powered by modern
cloud, AI, and platform engineering.
</p>
</div>
</div>
</div>

View File

@@ -93,7 +93,6 @@ const ContactSection = () => {
});
} catch (error) {
console.error('Contact form submission error:', error);
setSubmitStatus({
type: 'error',
message: error instanceof Error ? error.message : 'Failed to submit form. Please try again.'
@@ -413,7 +412,7 @@ const ContactSection = () => {
required
/>
<label htmlFor="privacy">
I agree to the <a href="/privacy-policy">Privacy Policy</a> and consent to being contacted by our team *
I agree to the <a href="/policy?type=privacy">Privacy Policy</a> and consent to being contacted by our team *
</label>
</div>
</div>

View File

@@ -6,18 +6,35 @@ const HomeBanner = () => {
const [currentTextIndex, setCurrentTextIndex] = useState(0);
const [isTransitioning, setIsTransitioning] = useState(false);
// Static banner slides data
// Fix viewport height for mobile browsers (especially iOS Safari)
useEffect(() => {
const setVH = () => {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
};
setVH();
window.addEventListener('resize', setVH);
window.addEventListener('orientationchange', setVH);
return () => {
window.removeEventListener('resize', setVH);
window.removeEventListener('orientationchange', setVH);
};
}, []);
// Static banner slides data based on actual services
const carouselTexts = [
{
id: 1,
badge: "Enterprise Solutions",
icon: "fa-solid fa-shield-halved",
heading: "Secure Enterprise Software",
badge: "Custom Development",
icon: "fa-solid fa-code",
heading: "Tailored Enterprise Software ",
highlight: "Development",
subheading: "for Modern Businesses",
description: "We build enterprise-grade software solutions with advanced security, scalability, and 24/7 support. Transform your business with our custom development services.",
subheading: "Aligned with Your Business Goals",
description: "We design and build custom digital solutions that deliver reliable, scalable, and future-ready applications, driving measurable value and competitive advantage for your enterprise.",
button_text: "Explore Solutions",
button_url: "/services",
button_url: "/services/custom-software-development",
is_active: true,
display_order: 1,
created_at: new Date().toISOString(),
@@ -25,14 +42,14 @@ const HomeBanner = () => {
},
{
id: 2,
badge: "API Integration",
icon: "fa-solid fa-plug",
heading: "Seamless API",
highlight: "Integration",
subheading: "& System Connectivity",
description: "Connect all your systems with our robust API development and integration services. Enable smooth data flow and unified workflows across your organization.",
button_text: "Learn More",
button_url: "/services/api-development",
badge: "Business Intelligence",
icon: "fa-solid fa-brain",
heading: "AI-Powered ",
highlight: "Analytics",
subheading: "Transform Data into Insights",
description: "Turn enterprise data into actionable intelligence with advanced AI and machine learning, enabling smarter decisions, performance optimization, and data-driven innovation.",
button_text: "Discover AI Solutions",
button_url: "/services/ai-powered-business-intelligence",
is_active: true,
display_order: 2,
created_at: new Date().toISOString(),
@@ -40,18 +57,33 @@ const HomeBanner = () => {
},
{
id: 3,
badge: "Cloud Migration",
icon: "fa-solid fa-cloud",
heading: "Cloud-First",
highlight: "Solutions",
subheading: "for Enterprise Scale",
description: "Migrate to the cloud with confidence. Our cloud solutions provide improved scalability, security, and cost-effectiveness for your enterprise operations.",
button_text: "Start Migration",
button_url: "/services/cloud-solutions",
badge: "System Integration",
icon: "fa-solid fa-plug",
heading: "Enterprise Systems ",
highlight: "Integration",
subheading: "Seamless Connectivity",
description: "Connect everything — from payment systems and ERP platforms to cloud services, enabling your enterprise to operate seamlessly across physical and digital environments.",
button_text: "View Integrations",
button_url: "/services/external-systems-integrations",
is_active: true,
display_order: 3,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 4,
badge: "Incident Management",
icon: "fa-solid fa-bell",
heading: "Intelligent Incident ",
highlight: "Management",
subheading: "Minimize Downtime & Protect Trust",
description: "Cloud-based incident management that empowers teams to detect, respond, and resolve issues faster, reducing downtime and maintaining customer confidence.",
button_text: "Learn More",
button_url: "/services/incident-management-saas",
is_active: true,
display_order: 4,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
];

View File

@@ -1,53 +1,77 @@
import Image from "next/legacy/image";
import Link from "next/link";
import thumb from "@/public/images/service/one.png";
import thumb from "@/public/images/leading.jpg";
const ServiceIntro = () => {
return (
<section className="tp-service pt-120 pb-120">
<div className="container">
<div className="row vertical-column-gap-md">
<div className="col-12 col-lg-5">
<div className="tp-service__thumb fade-img">
<Link href="service-single" className="w-100">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={thumb}
className="w-100 mh-300 parallax-image"
alt="Image"
/>
</div>
</div>
</Link>
</div>
<div className="container">
<div className="row vertical-column-gap-md">
<div className="col-12 col-lg-4">
<div className="tp-service__thumb" style={{ maxWidth: '400px', border: 'none', padding: 0, margin: 0, overflow: 'hidden', borderRadius: '8px' }}>
<Link href="services">
<Image
src={thumb}
alt="Enterprise Software Solutions"
width={400}
height={500}
objectFit="cover"
style={{ display: 'block', border: 'none', margin: 0, padding: 0 }}
/>
</Link>
</div>
<div className="col-12 col-lg-7 col-xl-6 offset-xl-1">
<div className="tp-service__content">
<h2 className="mt-8 title-anim text-secondary fw-7 mb-30 text-capitalize">
Leading Enterprise Software Solutions Provider.
</div>
<div className="col-12 col-lg-8">
<div className="tp-service__content">
<div className="tp-section-wrapper">
<h2 className="title-anim text-secondary fw-7 mb-30">
Accelerating Digital Transformation Through<br />
Mission-Critical Enterprise Software
</h2>
<div className="pl-100">
<p className="cur-lg">
Transform your enterprise with cutting-edge software solutions,
system integrations, and digital transformation services. We help
Fortune 500 companies modernize their technology infrastructure,
enhance security, and achieve operational excellence through
innovative software development.
</p>
<div className="mt-60">
<Link href="service-single" className="btn-anim btn-anim-light">
Our Solutions
<i className="fa-solid fa-arrow-trend-up"></i>
<span></span>
</Link>
</div>
</div>
<div className="pl-50">
<p className="cur-lg mb-25">
GNX partners with Fortune 40 companies and global enterprises to architect,
develop, and deploy business-critical software solutions that drive measurable
results. Our engineering teams deliver secure, scalable, and compliant systems
that power digital innovation across industries.
</p>
<p className="cur-lg mb-30">
From cloud-native architectures and enterprise integration platforms to
AI-powered analytics and legacy modernization, we provide end-to-end
technology solutions that reduce operational costs, enhance efficiency,
and deliver competitive advantage.
</p>
<div className="tp-service__features mb-40">
<ul className="list-unstyled">
<li className="mb-15">
<i className="fa-solid fa-circle-check text-primary me-10"></i>
<span className="fw-6">Enterprise-Grade Security & Compliance</span>
</li>
<li className="mb-15">
<i className="fa-solid fa-circle-check text-primary me-10"></i>
<span className="fw-6">Scalable Cloud-Native Architectures</span>
</li>
<li className="mb-15">
<i className="fa-solid fa-circle-check text-primary me-10"></i>
<span className="fw-6">24/7 Support & Dedicated Engineering Teams</span>
</li>
</ul>
</div>
<div className="mt-60">
<Link href="services" className="btn-anim btn-anim-light">
Explore Our Solutions
<i className="fa-solid fa-arrow-right"></i>
<span></span>
</Link>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</section>
);
};

View File

@@ -83,7 +83,6 @@ const Story = () => {
// Log when API data is loaded
useEffect(() => {
if (caseStudies.length > 0) {
console.log('Case studies loaded from API:', caseStudies.length);
}
}, [caseStudies]);
@@ -143,11 +142,9 @@ const Story = () => {
// Just filename or relative path
imageUrl = `${API_CONFIG.MEDIA_URL}/${item.thumbnail}`;
}
console.log('Case study image URL:', item.title, '→', imageUrl);
} else {
// Fallback to static image
imageUrl = getValidImageUrl('/images/case/one.png', FALLBACK_IMAGES.CASE_STUDY);
console.log('Using fallback image for:', item.title);
}
return (

View File

@@ -4,8 +4,7 @@ import gsap from "gsap";
import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
import Image from "next/legacy/image";
import Link from "next/link";
import thumbOne from "@/public/images/service/thumb-one.png";
import thumbTwo from "@/public/images/service/thumb-two.png";
const ServicesBanner = () => {
useEffect(() => {
@@ -55,12 +54,12 @@ const ServicesBanner = () => {
<div className="enterprise-banner__content">
<div className="banner-badge mb-4">
<span className="enterprise-badge">
Professional Services
Enterprise Services
</span>
</div>
<h1 className="enterprise-title mb-4">
The end-to-end bespoke software development agency you need
The end-to-end bespoke software development company you need
</h1>
<p className="enterprise-description mb-5">
@@ -106,12 +105,7 @@ const ServicesBanner = () => {
Scroll
<span className="arrow"></span>
</Link>
<div className="thumb-one">
<Image src={thumbOne} alt="Image" width={600} height={400} />
</div>
<div className="thumb-two">
<Image src={thumbTwo} alt="Image" width={600} height={400} />
</div>
</section>
);
};

View File

@@ -55,7 +55,27 @@ const CreateTicketForm = () => {
category: undefined
});
} catch (error: any) {
setSubmitError(error.message || 'Failed to submit ticket. Please try again.');
console.error('Ticket creation error:', error);
// Provide user-friendly error messages based on error type
let errorMessage = 'Failed to submit ticket. Please try again.';
if (error.message) {
if (error.message.includes('email')) {
errorMessage = 'There was an issue with your email address. Please check and try again.';
} else if (error.message.includes('network') || error.message.includes('fetch')) {
errorMessage = 'Network error. Please check your connection and try again.';
} else if (error.message.includes('validation')) {
errorMessage = 'Please check all required fields and try again.';
} else if (error.message.includes('server') || error.message.includes('500')) {
errorMessage = 'Server error. Our team has been notified. Please try again later.';
} else {
// Use the actual error message if it's user-friendly
errorMessage = error.message;
}
}
setSubmitError(errorMessage);
} finally {
setIsSubmitting(false);
}

View File

@@ -28,7 +28,6 @@ const KnowledgeBaseArticleModal = ({ slug, onClose }: KnowledgeBaseArticleModalP
await markArticleHelpful(slug, helpful);
setFeedbackGiven(true);
} catch (error) {
console.error('Error submitting feedback:', error);
}
};

View File

@@ -1,207 +0,0 @@
'use client';
import Image from 'next/image';
import { useState } from 'react';
interface OptimizedImageProps {
src: string;
alt: string;
width?: number;
height?: number;
className?: string;
priority?: boolean;
fill?: boolean;
sizes?: string;
quality?: number;
style?: React.CSSProperties;
objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
loading?: 'lazy' | 'eager';
}
/**
* OptimizedImage Component
*
* An enterprise-grade optimized image component that provides:
* - Automatic lazy loading
* - Responsive images with srcset
* - WebP/AVIF format support
* - Blur placeholder while loading
* - Error handling with fallback
* - Performance optimization
*
* @example
* <OptimizedImage
* src="/images/hero.jpg"
* alt="Hero banner showcasing our services"
* width={1200}
* height={600}
* priority={true}
* />
*/
export default function OptimizedImage({
src,
alt,
width,
height,
className = '',
priority = false,
fill = false,
sizes,
quality = 85,
style,
objectFit = 'cover',
loading,
}: OptimizedImageProps) {
const [imgSrc, setImgSrc] = useState(src);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
// Fallback image for errors
const fallbackImage = '/images/placeholder.png';
// Handle image load
const handleLoad = () => {
setIsLoading(false);
};
// Handle image error
const handleError = () => {
setHasError(true);
setIsLoading(false);
if (imgSrc !== fallbackImage) {
setImgSrc(fallbackImage);
}
};
// SEO-friendly alt text validation
const seoAlt = alt || 'GNX Soft - Enterprise Software Solutions';
// Validate alt text for SEO
if (process.env.NODE_ENV === 'development' && !alt) {
console.warn(
`OptimizedImage: Missing alt text for image "${src}". Alt text is crucial for SEO and accessibility.`
);
}
// Common image props
const imageProps = {
src: imgSrc,
alt: seoAlt,
className: `${className} ${isLoading ? 'image-loading' : 'image-loaded'}`,
onLoad: handleLoad,
onError: handleError,
quality,
loading: loading || (priority ? 'eager' : 'lazy'),
style: {
...style,
objectFit: objectFit as any,
},
};
// Use fill layout for responsive images
if (fill) {
return (
<div className={`optimized-image-wrapper ${hasError ? 'has-error' : ''}`}>
<Image
{...imageProps}
fill
sizes={sizes || '100vw'}
priority={priority}
/>
<style jsx>{`
.optimized-image-wrapper {
position: relative;
overflow: hidden;
}
.optimized-image-wrapper.has-error {
background: #f3f4f6;
}
:global(.image-loading) {
filter: blur(10px);
transform: scale(1.1);
transition: filter 0.3s ease, transform 0.3s ease;
}
:global(.image-loaded) {
filter: blur(0);
transform: scale(1);
}
`}</style>
</div>
);
}
// Standard layout with explicit dimensions
return (
<div className={`optimized-image-container ${hasError ? 'has-error' : ''}`}>
<Image
{...imageProps}
width={width}
height={height}
sizes={sizes}
priority={priority}
/>
<style jsx>{`
.optimized-image-container {
position: relative;
display: inline-block;
}
.optimized-image-container.has-error {
background: #f3f4f6;
border-radius: 4px;
}
:global(.image-loading) {
filter: blur(10px);
transform: scale(1.05);
transition: filter 0.4s ease, transform 0.4s ease;
}
:global(.image-loaded) {
filter: blur(0);
transform: scale(1);
}
`}</style>
</div>
);
}
/**
* Usage Examples:
*
* 1. Hero Image (Priority Loading):
* <OptimizedImage
* src="/images/hero.jpg"
* alt="Enterprise software development solutions"
* width={1920}
* height={1080}
* priority={true}
* sizes="100vw"
* />
*
* 2. Service Card Image (Lazy Loading):
* <OptimizedImage
* src="/images/service/custom-software.jpg"
* alt="Custom software development service icon"
* width={400}
* height={300}
* sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
* />
*
* 3. Background Image (Fill):
* <OptimizedImage
* src="/images/background.jpg"
* alt="Technology background pattern"
* fill={true}
* sizes="100vw"
* objectFit="cover"
* />
*
* 4. Logo (High Priority):
* <OptimizedImage
* src="/images/logo.png"
* alt="GNX Soft company logo"
* width={200}
* height={50}
* priority={true}
* quality={100}
* />
*/

View File

@@ -1,57 +0,0 @@
'use client';
import { ReactNode, CSSProperties } from 'react';
interface ProtectedImageProps {
src: string;
alt: string;
className?: string;
style?: CSSProperties;
width?: number;
height?: number;
showWatermark?: boolean;
priority?: boolean;
children?: ReactNode;
}
/**
* Protected Image Component
* Wraps images with protection against downloading and copying
*/
export default function ProtectedImage({
src,
alt,
className = '',
style = {},
width,
height,
showWatermark = false,
children
}: ProtectedImageProps) {
const wrapperClass = `protected-image-wrapper ${showWatermark ? 'watermarked-image' : ''} ${className}`;
return (
<div className={wrapperClass} style={style}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={src}
alt={alt}
width={width}
height={height}
draggable="false"
onContextMenu={(e) => e.preventDefault()}
onDragStart={(e) => e.preventDefault()}
style={{
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
userSelect: 'none',
pointerEvents: 'none'
}}
/>
{children}
</div>
);
}

View File

@@ -49,55 +49,116 @@ export const CookieConsentBanner: React.FC = () => {
return (
<AnimatePresence>
{/* Fullscreen overlay to center the banner */}
<motion.div
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 100, opacity: 0 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
className="cookie-consent-banner"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
style={{
position: 'fixed',
inset: 0,
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(17, 24, 39, 0.45)',
backdropFilter: 'blur(4px)',
}}
>
<div className="cookie-consent-banner__container">
<div className="cookie-consent-banner__content">
<div className="cookie-consent-banner__icon">
<i className="fas fa-cookie-bite"></i>
{/* Centered enterprise-style card */}
<motion.div
initial={{ opacity: 0, scale: 0.96, y: 8 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.98, y: 8 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
className="cookie-consent-banner"
style={{
width: 'min(680px, 92vw)',
background: '#0b1220',
color: '#e5e7eb',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 16,
boxShadow: '0 25px 70px rgba(0,0,0,0.45)',
padding: 24,
}}
>
<div className="cookie-consent-banner__container" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div className="cookie-consent-banner__content" style={{ display: 'flex', gap: 16 }}>
<div
className="cookie-consent-banner__icon"
style={{
width: 48,
height: 48,
borderRadius: 12,
display: 'grid',
placeItems: 'center',
background: 'linear-gradient(135deg, rgba(199, 213, 236, 0.39), rgba(147,197,253,0.08))',
color: '#93c5fd',
flex: '0 0 auto',
}}
>
<i className="fas fa-cookie-bite"></i>
</div>
<div className="cookie-consent-banner__text" style={{ display: 'grid', gap: 6 }}>
<h3 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>Cookie Preferences</h3>
<p style={{ margin: 0, lineHeight: 1.6, color: '#ffffff' }}>
We use only essential functional cookies to ensure our website works properly. We do not collect
personal data or use tracking cookies. Your privacy is important to us.
</p>
{config.showPrivacyNotice && (
<div className="cookie-consent-banner__links" style={{ marginTop: 6 }}>
<a
href={config.privacyPolicyUrl}
target="_blank"
rel="noopener noreferrer"
style={{ color: '#93c5fd', textDecoration: 'underline' }}
>
Privacy Policy
</a>
</div>
)}
</div>
</div>
<div className="cookie-consent-banner__text">
<h3>Cookie Preferences</h3>
<p>
We use only essential functional cookies to ensure our website works properly.
We do not collect personal data or use tracking cookies. Your privacy is important to us.
</p>
{config.showPrivacyNotice && (
<div className="cookie-consent-banner__links">
<a href={config.privacyPolicyUrl} target="_blank" rel="noopener noreferrer">
Privacy Policy
</a>
<a href={config.cookiePolicyUrl} target="_blank" rel="noopener noreferrer">
Cookie Policy
</a>
</div>
)}
<div
className="cookie-consent-banner__actions"
style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 8 }}
>
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--secondary"
onClick={showSettings}
style={{
padding: '10px 14px',
borderRadius: 10,
border: '1px solid rgba(255,255,255,0.12)',
background: 'transparent',
color: '#e5e7eb',
fontWeight: 600,
}}
>
<i className="fas fa-cog" style={{ marginRight: 8 }}></i>
Settings
</button>
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--primary"
onClick={acceptNecessary}
style={{
padding: '10px 14px',
borderRadius: 10,
border: '1px solid rgba(59,130,246,0.35)',
background: 'linear-gradient(135deg, rgba(59,130,246,0.25), rgba(37,99,235,0.35))',
color: '#ffffff',
fontWeight: 700,
}}
>
<i className="fas fa-check" style={{ marginRight: 8 }}></i>
Accept Functional Only
</button>
</div>
</div>
<div className="cookie-consent-banner__actions">
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--secondary"
onClick={showSettings}
>
<i className="fas fa-cog"></i>
Settings
</button>
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--primary"
onClick={acceptNecessary}
>
<i className="fas fa-check"></i>
Accept Functional Only
</button>
</div>
</div>
</motion.div>
</motion.div>
</AnimatePresence>
);

View File

@@ -64,8 +64,8 @@ const defaultPreferences: CookiePreferences = {
const defaultConfig: CookieConsentConfig = {
version: '2.0',
companyName: 'Your Company Name',
privacyPolicyUrl: '/privacy-policy',
cookiePolicyUrl: '/cookie-policy',
privacyPolicyUrl: '/policy?type=privacy',
cookiePolicyUrl: '/policy?type=privacy',
dataControllerEmail: 'privacy@yourcompany.com',
retentionPeriod: 365, // 1 year
enableAuditLog: true,
@@ -137,7 +137,6 @@ export const CookieConsentProvider: React.FC<{
}
}
} catch (error) {
console.warn('Failed to load cookie preferences:', error);
}
// Show banner if no valid consent found
@@ -171,7 +170,6 @@ export const CookieConsentProvider: React.FC<{
}));
}
} catch (error) {
console.warn('Failed to save cookie preferences:', error);
}
};
@@ -271,7 +269,6 @@ export const CookieConsentProvider: React.FC<{
try {
localStorage.removeItem(CONSENT_STORAGE_KEY);
} catch (error) {
console.warn('Failed to reset cookie preferences:', error);
}
setState({
@@ -288,7 +285,6 @@ export const CookieConsentProvider: React.FC<{
try {
localStorage.removeItem(CONSENT_STORAGE_KEY);
} catch (error) {
console.warn('Failed to withdraw consent:', error);
}
const auditEntry = config.enableAuditLog ? createAuditLogEntry('consent_withdrawn', defaultPreferences) : null;
@@ -386,7 +382,6 @@ export const useFunctional = () => {
try {
localStorage.setItem(`user-preference-${key}`, JSON.stringify(value));
} catch (error) {
console.warn('Failed to save user preference:', error);
}
}
};
@@ -397,7 +392,6 @@ export const useFunctional = () => {
const saved = localStorage.getItem(`user-preference-${key}`);
return saved ? JSON.parse(saved) : defaultValue;
} catch (error) {
console.warn('Failed to load user preference:', error);
return defaultValue;
}
}
@@ -406,7 +400,6 @@ export const useFunctional = () => {
const rememberUserAction = (action: string, data?: any) => {
if (isEnabled) {
console.log('User Action Remembered:', action, data);
// Implement your user action tracking logic here
}
};

View File

@@ -26,7 +26,6 @@ export const useFunctional = () => {
try {
localStorage.setItem(`user-preference-${key}`, JSON.stringify(value));
} catch (error) {
console.warn('Failed to save user preference:', error);
}
}
};
@@ -37,7 +36,6 @@ export const useFunctional = () => {
const saved = localStorage.getItem(`user-preference-${key}`);
return saved ? JSON.parse(saved) : defaultValue;
} catch (error) {
console.warn('Failed to load user preference:', error);
return defaultValue;
}
}
@@ -46,7 +44,6 @@ export const useFunctional = () => {
const rememberUserAction = (action: string, data?: any) => {
if (canShow) {
console.log('User Action Remembered:', action, data);
// Implement your user action tracking logic here
}
};

View File

@@ -24,7 +24,6 @@ const Header = () => {
// Find the Services menu item and update its submenu with API data
const servicesIndex = baseNavigation.findIndex(item => item.title === "Services");
if (servicesIndex !== -1 && apiServices.length > 0) {
console.log('Replacing services with API data:', apiServices);
baseNavigation[servicesIndex] = {
...baseNavigation[servicesIndex],
submenu: apiServices.map(service => ({
@@ -37,8 +36,6 @@ const Header = () => {
updated_at: service.updated_at
}))
} as any;
} else {
console.log('Using static services data. API services:', apiServices.length, 'Services index:', servicesIndex);
}
return baseNavigation;

11
gnx-react/dev.log Normal file
View File

@@ -0,0 +1,11 @@
> itify@0.1.0 dev
> next dev
▲ Next.js 15.5.3
- Local: http://localhost:3000
- Network: http://192.168.1.101:3000
- Environments: .env.local
✓ Starting...
✓ Ready in 2.4s

View File

@@ -131,7 +131,6 @@ class AboutServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching about page data:', error);
throw error;
}
}
@@ -155,7 +154,6 @@ class AboutServiceAPI {
const data = await response.json();
return data.results || data;
} catch (error) {
console.error('Error fetching about banners:', error);
throw error;
}
}
@@ -179,7 +177,6 @@ class AboutServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching about banner ${id}:`, error);
throw error;
}
}
@@ -203,7 +200,6 @@ class AboutServiceAPI {
const data = await response.json();
return data.results || data;
} catch (error) {
console.error('Error fetching about services:', error);
throw error;
}
}
@@ -227,7 +223,6 @@ class AboutServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching about service ${id}:`, error);
throw error;
}
}
@@ -251,7 +246,6 @@ class AboutServiceAPI {
const data = await response.json();
return data.results || data;
} catch (error) {
console.error('Error fetching about processes:', error);
throw error;
}
}
@@ -275,7 +269,6 @@ class AboutServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching about process ${id}:`, error);
throw error;
}
}
@@ -299,7 +292,6 @@ class AboutServiceAPI {
const data = await response.json();
return data.results || data;
} catch (error) {
console.error('Error fetching about journeys:', error);
throw error;
}
}
@@ -323,7 +315,6 @@ class AboutServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching about journey ${id}:`, error);
throw error;
}
}

View File

@@ -120,7 +120,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching blog posts:', error);
throw error;
}
},
@@ -142,7 +141,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching blog post:', error);
throw error;
}
},
@@ -164,7 +162,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching featured posts:', error);
throw error;
}
},
@@ -186,7 +183,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching latest posts:', error);
throw error;
}
},
@@ -208,7 +204,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching popular posts:', error);
throw error;
}
},
@@ -230,7 +225,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching related posts:', error);
throw error;
}
},
@@ -252,7 +246,6 @@ export const blogService = {
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
console.error('Error fetching blog categories:', error);
throw error;
}
},
@@ -274,7 +267,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching categories with posts:', error);
throw error;
}
},
@@ -296,7 +288,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching blog category:', error);
throw error;
}
},
@@ -318,7 +309,6 @@ export const blogService = {
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
console.error('Error fetching blog tags:', error);
throw error;
}
},
@@ -340,7 +330,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching posts by tag:', error);
throw error;
}
},
@@ -362,7 +351,6 @@ export const blogService = {
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
console.error('Error fetching blog authors:', error);
throw error;
}
},
@@ -384,7 +372,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching posts by author:', error);
throw error;
}
},
@@ -406,7 +393,6 @@ export const blogService = {
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
console.error('Error fetching comments:', error);
throw error;
}
},
@@ -429,7 +415,6 @@ export const blogService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error creating comment:', error);
throw error;
}
},

View File

@@ -55,12 +55,13 @@ export interface JobApplication {
class CareerService {
private baseUrl = `${API_BASE_URL}/api/career`;
/**
* Get all active job positions
*/
async getAllJobs(): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs/`, {
const response = await fetch(`${this.baseUrl}/jobs`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -75,7 +76,6 @@ class CareerService {
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
console.error('Error fetching jobs:', error);
throw error;
}
}
@@ -85,7 +85,7 @@ class CareerService {
*/
async getJobBySlug(slug: string): Promise<JobPosition> {
try {
const response = await fetch(`${this.baseUrl}/jobs/${slug}/`, {
const response = await fetch(`${this.baseUrl}/jobs/${slug}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -99,7 +99,6 @@ class CareerService {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching job:', error);
throw error;
}
}
@@ -109,7 +108,7 @@ class CareerService {
*/
async getFeaturedJobs(): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs/featured/`, {
const response = await fetch(`${this.baseUrl}/jobs/featured`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -124,7 +123,6 @@ class CareerService {
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
console.error('Error fetching featured jobs:', error);
throw error;
}
}
@@ -136,19 +134,15 @@ class CareerService {
try {
const formData = new FormData();
// Append all fields to FormData
// Required fields
formData.append('job', applicationData.job.toString());
formData.append('first_name', applicationData.first_name);
formData.append('last_name', applicationData.last_name);
formData.append('email', applicationData.email);
formData.append('consent', applicationData.consent.toString());
formData.append('resume', applicationData.resume);
// Append resume file
if (applicationData.resume) {
formData.append('resume', applicationData.resume);
}
// Append optional fields
// Optional fields (only append if they exist)
if (applicationData.phone) formData.append('phone', applicationData.phone);
if (applicationData.current_position) formData.append('current_position', applicationData.current_position);
if (applicationData.current_company) formData.append('current_company', applicationData.current_company);
@@ -160,24 +154,24 @@ class CareerService {
if (applicationData.website_url) formData.append('website_url', applicationData.website_url);
if (applicationData.available_from) formData.append('available_from', applicationData.available_from);
if (applicationData.notice_period) formData.append('notice_period', applicationData.notice_period);
if (applicationData.expected_salary) formData.append('expected_salary', applicationData.expected_salary.toString());
if (applicationData.expected_salary !== undefined) formData.append('expected_salary', applicationData.expected_salary.toString());
if (applicationData.salary_currency) formData.append('salary_currency', applicationData.salary_currency);
const response = await fetch(`${this.baseUrl}/applications/`, {
const response = await fetch(`${this.baseUrl}/applications`, {
method: 'POST',
body: formData,
// Don't set Content-Type header - browser will set it with boundary for multipart/form-data
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `Failed to submit application: ${response.statusText}`);
const errorData = await response.json().catch(() => ({}));
const errorMessage = errorData.error || errorData.message || errorData.detail || `HTTP ${response.status}: ${response.statusText}`;
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error submitting application:', error);
throw error;
}
}
@@ -187,7 +181,7 @@ class CareerService {
*/
async getJobsByDepartment(department: string): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs/?department=${department}`, {
const response = await fetch(`${this.baseUrl}/jobs?department=${department}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -202,7 +196,6 @@ class CareerService {
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
console.error('Error fetching jobs by department:', error);
throw error;
}
}
@@ -212,7 +205,7 @@ class CareerService {
*/
async getJobsByEmploymentType(employmentType: string): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs/?employment_type=${employmentType}`, {
const response = await fetch(`${this.baseUrl}/jobs?employment_type=${employmentType}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -227,7 +220,6 @@ class CareerService {
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
console.error('Error fetching jobs by employment type:', error);
throw error;
}
}

View File

@@ -115,7 +115,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching case studies:', error);
throw error;
}
},
@@ -140,7 +139,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching case study ${slug}:`, error);
throw error;
}
},
@@ -165,7 +163,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching featured case studies:', error);
throw error;
}
},
@@ -190,7 +187,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching latest case studies:', error);
throw error;
}
},
@@ -215,7 +211,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching popular case studies:', error);
throw error;
}
},
@@ -240,7 +235,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching related case studies for ${slug}:`, error);
throw error;
}
},
@@ -265,7 +259,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching case study categories:', error);
throw error;
}
},
@@ -290,7 +283,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching categories with case studies:', error);
throw error;
}
},
@@ -315,7 +307,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching clients:', error);
throw error;
}
},
@@ -340,7 +331,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching client ${slug}:`, error);
throw error;
}
},
@@ -365,7 +355,6 @@ export const caseStudyService = {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching case studies for client ${slug}:`, error);
throw error;
}
},

View File

@@ -51,7 +51,6 @@ class PolicyServiceAPI {
const data = await response.json();
return data.results || data;
} catch (error) {
console.error('Error fetching policies:', error);
throw error;
}
}
@@ -75,7 +74,6 @@ class PolicyServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching policy ${type}:`, error);
throw error;
}
}
@@ -99,7 +97,6 @@ class PolicyServiceAPI {
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching policy ${id}:`, error);
throw error;
}
}

View File

@@ -151,7 +151,6 @@ export const getTicketCategories = async (): Promise<TicketCategory[]> => {
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching ticket categories:', error);
throw error;
}
};
@@ -167,7 +166,6 @@ export const getTicketStatuses = async (): Promise<TicketStatus[]> => {
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching ticket statuses:', error);
throw error;
}
};
@@ -183,7 +181,6 @@ export const getTicketPriorities = async (): Promise<TicketPriority[]> => {
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching ticket priorities:', error);
throw error;
}
};
@@ -204,17 +201,38 @@ export const createTicket = async (data: CreateTicketData): Promise<SupportTicke
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
// Handle validation errors
if (response.status === 400 && errorData.user_email) {
throw new Error(errorData.user_email[0] || 'Email validation failed');
// Handle specific HTTP status codes
if (response.status === 400) {
// Handle validation errors
if (errorData.user_email) {
throw new Error(errorData.user_email[0] || 'Email validation failed');
}
if (errorData.title) {
throw new Error(errorData.title[0] || 'Title is required');
}
if (errorData.description) {
throw new Error(errorData.description[0] || 'Description is required');
}
if (errorData.user_name) {
throw new Error(errorData.user_name[0] || 'Name is required');
}
// Generic validation error
throw new Error(errorData.detail || errorData.message || 'Please check all required fields and try again');
} else if (response.status === 429) {
throw new Error('Too many requests. Please wait a moment and try again');
} else if (response.status >= 500) {
throw new Error('Server error. Please try again later');
} else if (response.status === 403) {
throw new Error('Access denied. Please contact support if this persists');
} else if (response.status === 404) {
throw new Error('Service not found. Please refresh the page and try again');
}
throw new Error(errorData.detail || errorData.message || 'Failed to create ticket');
throw new Error(errorData.detail || errorData.message || 'An unexpected error occurred');
}
return await response.json();
} catch (error) {
console.error('Error creating ticket:', error);
throw error;
}
};
@@ -239,7 +257,6 @@ export const checkTicketStatus = async (ticketNumber: string): Promise<SupportTi
}
return await response.json();
} catch (error) {
console.error('Error checking ticket status:', error);
throw error;
}
};
@@ -268,7 +285,6 @@ export const addTicketMessage = async (
if (!response.ok) throw new Error('Failed to add ticket message');
return await response.json();
} catch (error) {
console.error('Error adding ticket message:', error);
throw error;
}
};
@@ -284,7 +300,6 @@ export const getKnowledgeBaseCategories = async (): Promise<KnowledgeBaseCategor
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching knowledge base categories:', error);
throw error;
}
};
@@ -303,7 +318,6 @@ export const getKnowledgeBaseArticles = async (search?: string): Promise<Knowled
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching knowledge base articles:', error);
throw error;
}
};
@@ -319,7 +333,6 @@ export const getFeaturedArticles = async (): Promise<KnowledgeBaseArticle[]> =>
// Handle both array and paginated responses
return Array.isArray(data) ? data : (data.results || data);
} catch (error) {
console.error('Error fetching featured articles:', error);
throw error;
}
};
@@ -333,7 +346,6 @@ export const getKnowledgeBaseArticle = async (slug: string): Promise<KnowledgeBa
if (!response.ok) throw new Error('Failed to fetch knowledge base article');
return await response.json();
} catch (error) {
console.error('Error fetching knowledge base article:', error);
throw error;
}
};
@@ -349,7 +361,6 @@ export const getArticlesByCategory = async (categorySlug: string): Promise<Knowl
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching articles by category:', error);
throw error;
}
};
@@ -369,7 +380,6 @@ export const markArticleHelpful = async (slug: string, helpful: boolean): Promis
if (!response.ok) throw new Error('Failed to mark article helpful');
return await response.json();
} catch (error) {
console.error('Error marking article helpful:', error);
throw error;
}
};
@@ -385,7 +395,6 @@ export const getSupportSettings = async (): Promise<SupportSettings[]> => {
// Handle paginated response
return data.results || data;
} catch (error) {
console.error('Error fetching support settings:', error);
throw error;
}
};
@@ -399,7 +408,6 @@ export const getSupportSetting = async (settingName: string): Promise<SupportSet
if (!response.ok) throw new Error('Failed to fetch support setting');
return await response.json();
} catch (error) {
console.error('Error fetching support setting:', error);
throw error;
}
};

View File

@@ -53,7 +53,6 @@ export const useAbout = (): UseAboutReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching about page data:', err);
} finally {
setLoading(false);
}
@@ -87,7 +86,6 @@ export const useAboutBanners = (): UseAboutBannerReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching about banners:', err);
} finally {
setLoading(false);
}
@@ -121,7 +119,6 @@ export const useAboutServices = (): UseAboutServiceReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching about services:', err);
} finally {
setLoading(false);
}
@@ -155,7 +152,6 @@ export const useAboutProcesses = (): UseAboutProcessReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching about processes:', err);
} finally {
setLoading(false);
}
@@ -189,7 +185,6 @@ export const useAboutJourneys = (): UseAboutJourneyReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching about journeys:', err);
} finally {
setLoading(false);
}

View File

@@ -18,7 +18,6 @@ export const useJobs = () => {
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching jobs:', err);
} finally {
setLoading(false);
}
@@ -39,21 +38,27 @@ export const useJob = (slug: string) => {
const [error, setError] = useState<string | null>(null);
useEffect(() => {
console.log('🔍 useJob hook called with slug:', slug);
if (!slug) {
console.log('❌ No slug provided, setting loading to false');
setLoading(false);
return;
}
const fetchJob = async () => {
try {
console.log('📡 Fetching job data for slug:', slug);
setLoading(true);
const data = await careerService.getJobBySlug(slug);
console.log('✅ Job data fetched successfully:', data);
setJob(data);
setError(null);
} catch (err) {
console.error('❌ Error fetching job data:', err);
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching job:', err);
} finally {
console.log('🔄 Setting loading to false');
setLoading(false);
}
};
@@ -81,7 +86,6 @@ export const useFeaturedJobs = () => {
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching featured jobs:', err);
} finally {
setLoading(false);
}

View File

@@ -39,7 +39,6 @@ export const useCaseStudies = (params?: {
});
} catch (err) {
setError(err as Error);
console.error('Error fetching case studies:', err);
} finally {
setLoading(false);
}
@@ -79,7 +78,6 @@ export const useCaseStudy = (slug: string | null) => {
setCaseStudy(data);
} catch (err) {
setError(err as Error);
console.error(`Error fetching case study ${slug}:`, err);
} finally {
setLoading(false);
}
@@ -106,7 +104,6 @@ export const useFeaturedCaseStudies = () => {
setFeaturedCaseStudies(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching featured case studies:', err);
} finally {
setLoading(false);
}
@@ -133,7 +130,6 @@ export const useLatestCaseStudies = (limit: number = 6) => {
setLatestCaseStudies(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching latest case studies:', err);
} finally {
setLoading(false);
}
@@ -160,7 +156,6 @@ export const usePopularCaseStudies = (limit: number = 6) => {
setPopularCaseStudies(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching popular case studies:', err);
} finally {
setLoading(false);
}
@@ -192,7 +187,6 @@ export const useRelatedCaseStudies = (slug: string | null) => {
setRelatedCaseStudies(data);
} catch (err) {
setError(err as Error);
console.error(`Error fetching related case studies for ${slug}:`, err);
} finally {
setLoading(false);
}
@@ -219,7 +213,6 @@ export const useCaseStudyCategories = () => {
setCategories(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching case study categories:', err);
} finally {
setLoading(false);
}
@@ -246,7 +239,6 @@ export const useCategoriesWithCaseStudies = () => {
setCategories(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching categories with case studies:', err);
} finally {
setLoading(false);
}
@@ -273,7 +265,6 @@ export const useClients = () => {
setClients(data);
} catch (err) {
setError(err as Error);
console.error('Error fetching clients:', err);
} finally {
setLoading(false);
}
@@ -305,7 +296,6 @@ export const useClient = (slug: string | null) => {
setClient(data);
} catch (err) {
setError(err as Error);
console.error(`Error fetching client ${slug}:`, err);
} finally {
setLoading(false);
}
@@ -337,7 +327,6 @@ export const useClientCaseStudies = (slug: string | null) => {
setCaseStudies(data.results);
} catch (err) {
setError(err as Error);
console.error(`Error fetching case studies for client ${slug}:`, err);
} finally {
setLoading(false);
}

View File

@@ -32,7 +32,6 @@ export const usePolicies = (): UsePoliciesReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
console.error('Error fetching policies:', err);
} finally {
setLoading(false);
}
@@ -71,7 +70,6 @@ export const usePolicy = (type: 'privacy' | 'terms' | 'support' | null): UsePoli
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('An error occurred'));
console.error('Error fetching policy:', err);
} finally {
setIsLoading(false);
}
@@ -110,7 +108,6 @@ export const usePolicyById = (id: number | null): UsePolicyReturn => {
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('An error occurred'));
console.error('Error fetching policy:', err);
} finally {
setIsLoading(false);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -133,12 +133,12 @@ const nextConfig = {
// Redirects for SEO
async redirects() {
return [
// Redirect trailing slashes
{
source: '/:path+/',
destination: '/:path+',
permanent: true,
},
// Temporarily disabled - causing API issues
// {
// source: '/((?!api/).*)+/',
// destination: '/$1',
// permanent: true,
// },
]
},
// Rewrites for API proxy (Production: routes /api to backend through nginx)

View File

@@ -1,45 +0,0 @@
// Blog category data - Replace with API call to get blog categories
export const BlogCategoryButtons = [
{
id: 1,
title: "All",
slug: "all",
display_order: 1
},
{
id: 2,
title: "Enterprise Software",
slug: "enterprise-software",
display_order: 2
},
{
id: 3,
title: "Digital Transformation",
slug: "digital-transformation",
display_order: 3
},
{
id: 4,
title: "System Integration",
slug: "system-integration",
display_order: 4
},
{
id: 5,
title: "Cloud Solutions",
slug: "cloud-solutions",
display_order: 5
},
{
id: 6,
title: "Security",
slug: "security",
display_order: 6
},
{
id: 7,
title: "API Development",
slug: "api-development",
display_order: 7
}
];

View File

@@ -1,76 +0,0 @@
// Blog post data - Replace with API call to postsAPI.getAll()
import one from "@/public/images/blog/one.png";
import two from "@/public/images/blog/two.png";
import three from "@/public/images/blog/three.png";
import four from "@/public/images/blog/four.png";
import five from "@/public/images/blog/five.png";
import six from "@/public/images/blog/six.png";
import seven from "@/public/images/blog/seven.png";
import eight from "@/public/images/blog/eight.png";
export const BlogPostData = [
{
id: 1,
title: "The Future of Enterprise Software Architecture",
thumb: one,
category: "enterprise-software",
author: "Sarah Johnson",
date: "15 Jan 2024",
},
{
id: 2,
title: "Digital Transformation Strategies for Large Enterprises",
thumb: two,
category: "digital-transformation",
author: "Michael Chen",
date: "12 Jan 2024",
},
{
id: 3,
title: "API-First Approach to System Integration",
thumb: three,
category: "system-integration",
author: "Emily Rodriguez",
date: "10 Jan 2024",
},
{
id: 4,
title: "Cloud Migration Best Practices for Enterprise",
thumb: four,
category: "cloud-solutions",
author: "David Thompson",
date: "08 Jan 2024",
},
{
id: 5,
title: "Enterprise Security in the Digital Age",
thumb: five,
category: "security",
author: "Sarah Johnson",
date: "05 Jan 2024",
},
{
id: 6,
title: "Building Scalable API Architectures",
thumb: six,
category: "api-development",
author: "Michael Chen",
date: "03 Jan 2024",
},
{
id: 7,
title: "Microservices Architecture for Enterprise Applications",
thumb: seven,
category: "enterprise-software",
author: "Emily Rodriguez",
date: "01 Jan 2024",
},
{
id: 8,
title: "Data Analytics and Business Intelligence Solutions",
thumb: eight,
category: "digital-transformation",
author: "David Thompson",
date: "29 Dec 2023",
}
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -406,6 +406,10 @@
font-size: 11px;
padding: 6px 14px;
letter-spacing: 0.8px;
background: rgba(14, 165, 233, 0.15) !important;
border-color: rgba(14, 165, 233, 0.5) !important;
color: #0ea5e9 !important;
font-weight: 600;
i {
font-size: 12px;
@@ -1096,12 +1100,17 @@
margin-bottom: 8px;
.badge {
font-size: 9px;
padding: 4px 10px;
font-size: 10px;
padding: 5px 12px;
letter-spacing: 0.4px;
background: rgba(14, 165, 233, 0.2) !important;
border-color: rgba(14, 165, 233, 0.6) !important;
color: #3dd5f3 !important;
font-weight: 700;
box-shadow: 0 0 10px rgba(14, 165, 233, 0.3);
i {
font-size: 10px;
font-size: 11px;
}
}
}
@@ -1183,6 +1192,354 @@
}
}
// Extra extra small screens (< 375px)
@media only screen and (max-width: 374.98px) {
.modern-banner {
min-height: 100vh;
.container {
padding: 20px 0;
}
.content-center {
padding: 0 8px;
.badge-container {
margin-bottom: 6px;
.badge {
font-size: 9px;
padding: 4px 10px;
letter-spacing: 0.3px;
gap: 4px;
background: rgba(14, 165, 233, 0.25) !important;
border-color: rgba(14, 165, 233, 0.7) !important;
color: #5de4ff !important;
font-weight: 700;
box-shadow: 0 0 12px rgba(14, 165, 233, 0.4);
i {
font-size: 10px;
}
}
}
.main-heading {
font-size: clamp(1rem, 7vw, 1.3rem);
margin-bottom: 8px;
line-height: 1.1;
}
.description {
font-size: 11px;
margin-bottom: 14px;
line-height: 1.3;
}
.carousel-indicators {
margin-bottom: 14px;
gap: 3px;
.indicator {
width: 3px;
height: 3px;
}
}
.cta-section {
gap: 3px;
margin-bottom: 16px;
.cta-primary,
.cta-secondary {
max-width: 220px;
padding: 7px 12px;
font-size: 10px;
i {
font-size: 10px;
}
}
}
.trust-indicators {
gap: 10px;
.trust-item {
min-width: 55px;
.trust-number {
font-size: 0.9rem;
margin-bottom: 2px;
}
.trust-label {
font-size: 6.5px;
letter-spacing: 0.3px;
}
}
}
}
.banner-background {
.gradient-orb {
&.orb-1 {
width: 100px;
height: 100px;
}
&.orb-2 {
width: 80px;
height: 80px;
}
&.orb-3 {
width: 60px;
height: 60px;
}
}
}
}
}
// Landscape orientation for phones
@media only screen and (max-height: 500px) and (orientation: landscape) {
.modern-banner {
min-height: auto;
height: auto;
padding: 20px 0;
.container {
padding: 20px 0;
}
.banner-content {
margin-bottom: 10px;
}
.content-center {
max-width: 100%;
padding: 0 15px;
.badge-container {
margin-bottom: 8px;
.badge {
font-size: 10px;
padding: 5px 12px;
background: rgba(14, 165, 233, 0.18) !important;
border-color: rgba(14, 165, 233, 0.55) !important;
color: #3dd5f3 !important;
font-weight: 600;
box-shadow: 0 0 8px rgba(14, 165, 233, 0.25);
}
}
.main-heading {
font-size: clamp(1.2rem, 3vh, 1.6rem);
margin-bottom: 8px;
line-height: 1.1;
}
.description {
font-size: 12px;
margin-bottom: 12px;
line-height: 1.3;
max-width: 90%;
}
.carousel-indicators {
margin-bottom: 12px;
.indicator {
width: 5px;
height: 5px;
}
}
.cta-section {
flex-direction: row;
gap: 8px;
margin-bottom: 12px;
justify-content: center;
.cta-primary,
.cta-secondary {
width: auto;
max-width: 180px;
padding: 8px 16px;
font-size: 11px;
}
}
.trust-indicators {
gap: 15px;
margin-bottom: 10px;
.trust-item {
min-width: 65px;
.trust-number {
font-size: 1rem;
margin-bottom: 2px;
}
.trust-label {
font-size: 7px;
}
}
}
}
.banner-background {
.gradient-orb {
opacity: 0.2;
&.orb-1 {
width: 150px;
height: 150px;
}
&.orb-2 {
width: 100px;
height: 100px;
}
&.orb-3 {
display: none;
}
}
.enterprise-bg-elements {
display: none;
}
}
.scroll-indicator {
display: none;
}
}
}
// Tablet landscape (iPad, etc.)
@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) {
.modern-banner {
min-height: 90vh;
.container {
padding: 40px 0;
}
.content-center {
max-width: 700px;
.main-heading {
font-size: clamp(2rem, 4vw, 3rem);
margin-bottom: 14px;
}
.description {
font-size: 15px;
margin-bottom: 20px;
max-width: 600px;
}
.cta-section {
gap: 10px;
margin-bottom: 25px;
}
.trust-indicators {
gap: 25px;
}
}
.banner-background {
.enterprise-bg-elements {
opacity: 0.1;
.flying-code,
.request-response-data,
.space-data-generation {
display: none;
}
}
}
}
}
// Large tablets and small laptops (portrait)
@media only screen and (min-width: 768px) and (max-width: 991.98px) and (orientation: portrait) {
.modern-banner {
min-height: 100vh;
.content-center {
max-width: 600px;
padding: 0 30px;
.main-heading {
font-size: clamp(1.8rem, 4vw, 2.5rem);
margin-bottom: 16px;
}
.description {
font-size: 15px;
margin-bottom: 24px;
}
.cta-section {
flex-direction: row;
gap: 12px;
margin-bottom: 30px;
.cta-primary,
.cta-secondary {
width: auto;
min-width: 180px;
}
}
}
}
}
// Touch device optimizations
@media (hover: none) and (pointer: coarse) {
.modern-banner {
.content-center {
.carousel-indicators {
.indicator {
width: 10px;
height: 10px;
margin: 0 2px;
&:not(.active) {
opacity: 0.5;
}
}
}
.cta-section {
.cta-primary,
.cta-secondary {
min-height: 44px; // Apple's minimum touch target size
display: flex;
align-items: center;
justify-content: center;
}
}
}
// Reduce animations for better performance on mobile devices
.banner-background {
.enterprise-bg-elements {
* {
animation-duration: 1.5x !important; // Slow down animations
}
}
}
}
}
@media only screen and (min-width: 1400px) {
.tp-banner {
.tp-banner__content {

View File

@@ -70,20 +70,29 @@
h3 {
font-size: var(--text-lg);
font-weight: var(--font-weight-semibold);
color: var(--secondary-800);
color: #ffffff !important;
margin: 0 0 var(--space-2) 0;
line-height: var(--leading-tight);
}
p {
font-size: var(--text-sm);
color: var(--secondary-600);
color: #ffffff !important;
margin: 0 0 var(--space-3) 0;
line-height: var(--leading-relaxed);
max-width: 600px;
}
}
// Force white color with higher specificity
&.cookie-consent-banner .cookie-consent-banner__text h3 {
color: #ffffff !important;
}
&.cookie-consent-banner .cookie-consent-banner__text p {
color: #ffffff !important;
}
&__links {
display: flex;
gap: var(--space-4);
@@ -618,11 +627,11 @@
border-top-color: var(--secondary-700);
&__text h3 {
color: var(--secondary-100);
color: #ffffff !important;
}
&__text p {
color: var(--secondary-300);
color: #ffffff !important;
}
}

View File

@@ -2,12 +2,21 @@
--------- (4.02) banner styles start ---------
==== */
// CSS custom property for dynamic viewport height (better mobile support)
:root {
--vh: 1vh;
}
// 4.02.01 modern banner styles start
.modern-banner {
position: relative;
height: 100vh;
height: calc(var(--vh, 1vh) * 100); // Dynamic viewport height for mobile browsers
min-height: 100vh;
min-height: -webkit-fill-available; // iOS viewport fix
background: #0a0a0a;
overflow: hidden;
overflow-x: hidden; // Prevent horizontal scroll
display: flex;
flex-direction: column;
justify-content: center;
@@ -610,6 +619,23 @@
flex-direction: column;
justify-content: center;
padding: 80px 0;
width: 100%;
max-width: 1200px;
margin: 0 auto;
@media (max-width: 1199.98px) {
max-width: 960px;
}
@media (max-width: 991.98px) {
max-width: 720px;
}
@media (max-width: 767.98px) {
max-width: 100%;
padding-left: 15px;
padding-right: 15px;
}
}
.banner-content {
@@ -645,6 +671,31 @@
i {
font-size: 14px;
}
// Mobile visibility enhancement
@media (max-width: 991.98px) {
background: linear-gradient(135deg, rgba(30, 64, 175, 0.25), rgba(14, 165, 233, 0.25));
border: 2px solid rgba(14, 165, 233, 0.7);
color: #3dd5f3;
font-weight: 700;
box-shadow: 0 0 15px rgba(14, 165, 233, 0.35),
0 4px 10px rgba(0, 0, 0, 0.3);
text-shadow: 0 0 8px rgba(14, 165, 233, 0.5);
i {
filter: drop-shadow(0 0 4px rgba(14, 165, 233, 0.6));
}
}
@media (max-width: 575.98px) {
background: rgba(14, 165, 233, 0.3);
border: 2px solid rgba(14, 165, 233, 0.8);
color: #5de4ff;
font-weight: 700;
box-shadow: 0 0 20px rgba(14, 165, 233, 0.5),
0 4px 12px rgba(0, 0, 0, 0.4);
text-shadow: 0 0 10px rgba(14, 165, 233, 0.7);
}
}
}
@@ -703,6 +754,7 @@
gap: 8px;
justify-content: center;
margin-bottom: 24px;
padding: 8px 0;
.indicator {
width: 8px;
@@ -712,6 +764,9 @@
border: none;
cursor: pointer;
transition: all 0.3s ease;
padding: 0;
user-select: none;
-webkit-tap-highlight-color: transparent;
&.active {
background: #0ea5e9;
@@ -719,10 +774,19 @@
box-shadow: 0 0 10px rgba(14, 165, 233, 0.5);
}
&:hover {
&:hover:not(.active) {
background: rgba(255, 255, 255, 0.6);
transform: scale(1.1);
}
&:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.3);
}
&:focus:not(:focus-visible) {
box-shadow: none;
}
}
}
@@ -746,12 +810,19 @@
transition: all 0.3s ease;
box-shadow: 0 6px 24px rgba(30, 64, 175, 0.3);
font-size: 15px;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.4);
}
&:active {
transform: translateY(0);
}
i {
font-size: 15px;
transition: transform 0.3s ease;
@@ -776,6 +847,9 @@
transition: all 0.3s ease;
backdrop-filter: blur(10px);
font-size: 15px;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(255, 255, 255, 0.1);
@@ -783,6 +857,10 @@
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
i {
font-size: 15px;
}
@@ -833,6 +911,8 @@
z-index: 20;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
-webkit-tap-highlight-color: transparent;
&:hover {
color: #0ea5e9;
@@ -853,6 +933,10 @@
font-size: 16px;
}
}
@media (max-width: 991.98px) {
display: none;
}
}
@keyframes float {