This commit is contained in:
2025-09-12 15:24:06 +05:30
parent b92128c825
commit acbd3c8421
21 changed files with 830 additions and 1173 deletions

View File

@@ -1,236 +1,285 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Building2, Users, Target, Eye, Award, Mail, Phone, MapPin } from "lucide-react";
import api from "@/lib/api";
import { Building2, Users, Target, Eye, Heart, Shield, Zap, ArrowRight } from "lucide-react";
export default function AboutUsPage() {
const { data: aboutData, isLoading, error } = useQuery({
queryKey: ["aboutUs"],
queryFn: async () => {
const { data } = await api.get("/configurations/about-us");
return data;
},
});
if (isLoading) {
return (
<div className="pt-20 px-4">
<div className="max-w-4xl mx-auto">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/3 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
return (
<div className="min-h-screen bg-white">
{/* Hero Section */}
<section className="pt-24 pb-20 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center">
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-extrabold text-gray-900 tracking-tight">
About{" "}
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
PaisaAds
</span>
</h1>
<div className="mt-8 max-w-3xl mx-auto">
<p className="text-xl sm:text-2xl text-gray-600 leading-relaxed font-light">
Your trusted platform for buying, selling, and discovering amazing deals.
</p>
<p className="mt-4 text-lg text-gray-500 leading-relaxed">
We connect communities through classified advertisements, making commerce simple and accessible for everyone.
</p>
</div>
</div>
</div>
</div>
);
}
</section>
if (error || !aboutData) {
return (
<div className="pt-20 px-4">
<div className="max-w-4xl mx-auto text-center">
<h1 className="text-3xl font-bold text-gray-900 mb-4">About Us</h1>
<p className="text-gray-600">Information not available at the moment.</p>
</div>
</div>
);
}
return (
<div className="pt-20 px-4 pb-12">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">About Us</h1>
{aboutData.companyOverview && (
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
{aboutData.companyOverview}
{/* Mission & Vision Section */}
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-white">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4">Our Purpose</h2>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
Driven by a clear mission and vision to transform how people connect and trade
</p>
)}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
{/* Mission */}
{aboutData.mission && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Target className="h-5 w-5 text-blue-600" />
Our Mission
</CardTitle>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
<Card className="group relative overflow-hidden border-0 shadow-lg hover:shadow-2xl transition-all duration-300 bg-gradient-to-br from-blue-50 to-blue-100">
<CardHeader className="relative p-8 pb-4">
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-blue-600 rounded-xl shadow-lg group-hover:scale-110 transition-transform duration-300">
<Target className="h-7 w-7 text-white" />
</div>
<CardTitle className="text-2xl font-bold text-gray-900">Our Mission</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-gray-700">{aboutData.mission}</p>
<CardContent className="relative p-8 pt-0">
<p className="text-gray-700 leading-relaxed text-lg">
To create the most user-friendly and secure marketplace where individuals and businesses
can easily connect, trade, and grow their communities through classified advertisements.
</p>
</CardContent>
</Card>
)}
{/* Vision */}
{aboutData.vision && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Eye className="h-5 w-5 text-green-600" />
Our Vision
</CardTitle>
<Card className="group relative overflow-hidden border-0 shadow-lg hover:shadow-2xl transition-all duration-300 bg-gradient-to-br from-green-50 to-green-100">
<CardHeader className="relative p-8 pb-4">
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-green-600 rounded-xl shadow-lg group-hover:scale-110 transition-transform duration-300">
<Eye className="h-7 w-7 text-white" />
</div>
<CardTitle className="text-2xl font-bold text-gray-900">Our Vision</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-gray-700">{aboutData.vision}</p>
<CardContent className="relative p-8 pt-0">
<p className="text-gray-700 leading-relaxed text-lg">
To become the leading classified ads platform that empowers millions of users to discover
opportunities, build connections, and achieve their buying and selling goals with ease.
</p>
</CardContent>
</Card>
)}
</div>
</div>
</section>
{/* Values */}
{aboutData.values && aboutData.values.length > 0 && (
<Card className="mb-12">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Building2 className="h-5 w-5 text-purple-600" />
Our Values
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{aboutData.values.map((value: string, index: number) => (
<Badge key={index} variant="secondary" className="p-3 text-center justify-center">
{value}
</Badge>
))}
{/* Core Values Section */}
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-gray-100">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="flex items-center justify-center gap-3 mb-4">
<div className="p-3 bg-gradient-to-r from-purple-600 to-pink-600 rounded-xl shadow-lg">
<Heart className="h-7 w-7 text-white" />
</div>
</CardContent>
</Card>
)}
<h2 className="text-4xl font-bold text-gray-900">Our Core Values</h2>
</div>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
The principles that guide everything we do and shape our platform's future
</p>
</div>
{/* History */}
{aboutData.history && (
<Card className="mb-12">
<CardHeader>
<CardTitle>Our History</CardTitle>
</CardHeader>
<CardContent>
<div
className="prose max-w-none text-gray-700"
dangerouslySetInnerHTML={{ __html: aboutData.history }}
/>
</CardContent>
</Card>
)}
{/* Team Members */}
{aboutData.teamMembers && aboutData.teamMembers.length > 0 && (
<Card className="mb-12">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5 text-orange-600" />
Our Team
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{aboutData.teamMembers.map((member: any, index: number) => (
<div key={index} className="text-center">
<Avatar className="h-24 w-24 mx-auto mb-4">
<AvatarImage src={member.imageUrl} alt={member.name} />
<AvatarFallback className="text-lg">
{member.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase()}
</AvatarFallback>
</Avatar>
<h3 className="font-semibold text-lg">{member.name}</h3>
<p className="text-sm text-gray-600 mb-2">{member.position}</p>
{member.bio && (
<p className="text-sm text-gray-700 mb-3">{member.bio}</p>
)}
{member.socialLinks && member.socialLinks.length > 0 && (
<div className="flex justify-center gap-2">
{member.socialLinks.map((link: string, linkIndex: number) => (
<a
key={linkIndex}
href={link}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 text-sm"
>
Link {linkIndex + 1}
</a>
))}
</div>
)}
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Achievements */}
{aboutData.achievements && aboutData.achievements.length > 0 && (
<Card className="mb-12">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Award className="h-5 w-5 text-yellow-600" />
Our Achievements
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{aboutData.achievements.map((achievement: string, index: number) => (
<div key={index} className="flex items-start gap-3">
<Award className="h-5 w-5 text-yellow-600 mt-0.5 flex-shrink-0" />
<p className="text-gray-700">{achievement}</p>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Contact Information */}
{aboutData.contactInfo && (
<Card>
<CardHeader>
<CardTitle>Get in Touch</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{aboutData.contactInfo.email && (
<div className="flex items-center gap-3">
<Mail className="h-5 w-5 text-blue-600" />
<div>
<p className="font-medium">Email</p>
<p className="text-sm text-gray-600">{aboutData.contactInfo.email}</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="group">
<Card className="h-full border-0 shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-2">
<CardContent className="p-8 text-center">
<div className="mb-6">
<div className="w-20 h-20 bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl mx-auto flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform duration-300">
<Shield className="h-10 w-10 text-white" />
</div>
</div>
)}
{aboutData.contactInfo.phone && (
<div className="flex items-center gap-3">
<Phone className="h-5 w-5 text-green-600" />
<div>
<p className="font-medium">Phone</p>
<p className="text-sm text-gray-600">{aboutData.contactInfo.phone}</p>
<h3 className="text-xl font-bold text-gray-900 mb-4">Trust & Security</h3>
<p className="text-gray-600 leading-relaxed">
We prioritize user safety with robust verification systems and secure transactions, ensuring peace of mind for every interaction.
</p>
</CardContent>
</Card>
</div>
<div className="group">
<Card className="h-full border-0 shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-2">
<CardContent className="p-8 text-center">
<div className="mb-6">
<div className="w-20 h-20 bg-gradient-to-br from-green-500 to-green-600 rounded-2xl mx-auto flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform duration-300">
<Users className="h-10 w-10 text-white" />
</div>
</div>
)}
{aboutData.contactInfo.address && (
<div className="flex items-center gap-3">
<MapPin className="h-5 w-5 text-red-600" />
<div>
<p className="font-medium">Address</p>
<p className="text-sm text-gray-600">{aboutData.contactInfo.address}</p>
<h3 className="text-xl font-bold text-gray-900 mb-4">Community First</h3>
<p className="text-gray-600 leading-relaxed">
Building strong local communities through meaningful connections and interactions that benefit everyone involved.
</p>
</CardContent>
</Card>
</div>
<div className="group">
<Card className="h-full border-0 shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-2">
<CardContent className="p-8 text-center">
<div className="mb-6">
<div className="w-20 h-20 bg-gradient-to-br from-purple-500 to-purple-600 rounded-2xl mx-auto flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform duration-300">
<Zap className="h-10 w-10 text-white" />
</div>
</div>
)}
<h3 className="text-xl font-bold text-gray-900 mb-4">Innovation</h3>
<p className="text-gray-600 leading-relaxed">
Continuously improving our platform with cutting-edge features and technology to enhance user experience.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
</section>
{/* What We Offer Section */}
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-white">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4">What We Offer</h2>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
Discover the powerful features that make PaisaAds the perfect platform for your needs
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-12">
<div className="group text-center">
<div className="relative">
<div className="w-24 h-24 bg-gradient-to-br from-blue-500 to-blue-600 rounded-3xl mx-auto mb-6 flex items-center justify-center shadow-xl group-hover:scale-110 transition-all duration-300">
<Building2 className="h-12 w-12 text-white" />
</div>
<div className="absolute -inset-1 bg-gradient-to-br from-blue-400 to-blue-600 rounded-3xl blur opacity-25 group-hover:opacity-40 transition duration-300"></div>
</div>
</CardContent>
</Card>
)}
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">Multiple Ad Types</h3>
<p className="text-gray-600 leading-relaxed text-lg">
Post line ads, poster ads, and video ads to showcase your products and services with maximum impact and engagement.
</p>
</div>
<div className="group text-center">
<div className="relative">
<div className="w-24 h-24 bg-gradient-to-br from-green-500 to-green-600 rounded-3xl mx-auto mb-6 flex items-center justify-center shadow-xl group-hover:scale-110 transition-all duration-300">
<Target className="h-12 w-12 text-white" />
</div>
<div className="absolute -inset-1 bg-gradient-to-br from-green-400 to-green-600 rounded-3xl blur opacity-25 group-hover:opacity-40 transition duration-300"></div>
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">Smart Categories</h3>
<p className="text-gray-600 leading-relaxed text-lg">
Organize and discover ads through our comprehensive category system designed for precise targeting and easy navigation.
</p>
</div>
<div className="group text-center">
<div className="relative">
<div className="w-24 h-24 bg-gradient-to-br from-purple-500 to-purple-600 rounded-3xl mx-auto mb-6 flex items-center justify-center shadow-xl group-hover:scale-110 transition-all duration-300">
<Eye className="h-12 w-12 text-white" />
</div>
<div className="absolute -inset-1 bg-gradient-to-br from-purple-400 to-purple-600 rounded-3xl blur opacity-25 group-hover:opacity-40 transition duration-300"></div>
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">Easy Discovery</h3>
<p className="text-gray-600 leading-relaxed text-lg">
Advanced search and filtering tools help you find exactly what you're looking for quickly and efficiently.
</p>
</div>
</div>
</div>
</section>
{/* Platform Features Section */}
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-gray-100">
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">
<Card className="group border-0 shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden bg-gradient-to-br from-orange-50 to-orange-100">
<CardHeader className="relative p-8 pb-4">
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl shadow-lg group-hover:scale-110 transition-transform duration-300">
<Building2 className="h-7 w-7 text-white" />
</div>
<CardTitle className="text-2xl font-bold text-gray-900">Our Platform</CardTitle>
</div>
</CardHeader>
<CardContent className="relative p-8 pt-0">
<p className="text-gray-700 leading-relaxed text-lg mb-6">
PaisaAds is built with modern technology to provide a seamless experience for both buyers and sellers.
Our platform supports various types of advertisements designed for maximum engagement.
</p>
<div className="flex flex-wrap gap-3">
<Badge className="px-4 py-2 bg-blue-100 text-blue-800 hover:bg-blue-200 transition-colors font-medium">Line Ads</Badge>
<Badge className="px-4 py-2 bg-green-100 text-green-800 hover:bg-green-200 transition-colors font-medium">Poster Ads</Badge>
<Badge className="px-4 py-2 bg-purple-100 text-purple-800 hover:bg-purple-200 transition-colors font-medium">Video Ads</Badge>
<Badge className="px-4 py-2 bg-orange-100 text-orange-800 hover:bg-orange-200 transition-colors font-medium">Smart Categories</Badge>
</div>
</CardContent>
</Card>
<Card className="group border-0 shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden bg-gradient-to-br from-teal-50 to-teal-100">
<CardHeader className="relative p-8 pb-4">
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl shadow-lg group-hover:scale-110 transition-transform duration-300">
<Users className="h-7 w-7 text-white" />
</div>
<CardTitle className="text-2xl font-bold text-gray-900">Join Our Community</CardTitle>
</div>
</CardHeader>
<CardContent className="relative p-8 pt-0">
<p className="text-gray-700 leading-relaxed text-lg mb-4">
Whether you're looking to sell items you no longer need, promote your business,
or find great deals in your area, PaisaAds provides the tools and community to help you succeed.
</p>
<p className="text-gray-700 leading-relaxed text-lg">
Join thousands of users who trust PaisaAds for their buying and selling needs.
</p>
</CardContent>
</Card>
</div>
</div>
</section>
{/* Call to Action Section */}
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600">
<div className="max-w-4xl mx-auto text-center">
<div className="relative bg-white/10 backdrop-blur-sm rounded-3xl p-12 sm:p-16">
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-6">
Ready to Get Started?
</h2>
<p className="text-xl text-white/90 mb-10 max-w-2xl mx-auto leading-relaxed">
Join PaisaAds today and discover a world of opportunities right at your fingertips.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<a
href="/search"
className="group bg-white hover:bg-gray-50 text-gray-900 px-8 py-4 rounded-xl font-semibold transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-105 flex items-center gap-2"
>
Browse Ads
<ArrowRight className="h-5 w-5 group-hover:translate-x-1 transition-transform duration-300" />
</a>
<a
href="/dashboard/post-ad"
className="group bg-transparent hover:bg-white/10 text-white px-8 py-4 rounded-xl font-semibold border-2 border-white/30 hover:border-white/50 transition-all duration-300 flex items-center gap-2"
>
Post Your Ad
<ArrowRight className="h-5 w-5 group-hover:translate-x-1 transition-transform duration-300" />
</a>
</div>
</div>
</div>
</section>
{/* Footer Spacing */}
<div className="pb-16"></div>
</div>
);
}

View File

@@ -20,7 +20,7 @@ function VideoAdCard({ ad }: { ad: VideoAd }) {
const videoUrl = `/api/images?imageName=${ad.image.fileName}`;
return (
<div className="relative overflow-hidden rounded-lg shadow-md group w-full aspect-[1/1.2]">
<div className="relative overflow-hidden rounded-lg shadow-md group w-full aspect-[4/5] md:aspect-[1/1.2]">
{/* Video */}
<div className="relative h-full w-full overflow-hidden bg-gray-200">
<video
@@ -90,7 +90,7 @@ function EnhancedPosterAdCard({ ad }: { ad: PosterAd }) {
const imageUrl = `/api/images?imageName=${ad.image.fileName}`;
return (
<div className="relative overflow-hidden rounded-lg shadow-md group w-full aspect-[1/1.2]">
<div className="relative overflow-hidden rounded-lg shadow-md group w-full aspect-[4/5] md:aspect-[1/1.2]">
{/* Image */}
<div className="relative h-full w-full overflow-hidden">
<img
@@ -281,15 +281,32 @@ export default function PosterVideoAdSides({
}
return (
<div className="flex items-center flex-col gap-5">
{adsGroupedByPosition.map((positionGroup, groupIndex) => (
<div key={`${side}-${positionGroup.position}-${groupIndex}`} className="w-full">
<AdCarousel
ads={positionGroup.ads}
positionName={`${targetSide}-${positionGroup.position}`}
/>
<div className="flex items-center flex-col md:gap-5 gap-3">
{/* Mobile: Horizontal scrolling */}
<div className="md:hidden w-full overflow-x-auto">
<div className="flex gap-3 pb-2" style={{ width: `${adsGroupedByPosition.length * 250}px` }}>
{adsGroupedByPosition.slice(0, 6).map((positionGroup, groupIndex) => (
<div key={`${side}-${positionGroup.position}-${groupIndex}`} className="min-w-[240px] flex-shrink-0">
<AdCarousel
ads={positionGroup.ads}
positionName={`${targetSide}-${positionGroup.position}`}
/>
</div>
))}
</div>
))}
</div>
{/* Desktop: Vertical layout */}
<div className="hidden md:flex md:flex-col md:gap-5 md:items-center">
{adsGroupedByPosition.map((positionGroup, groupIndex) => (
<div key={`${side}-${positionGroup.position}-${groupIndex}`} className="w-full">
<AdCarousel
ads={positionGroup.ads}
positionName={`${targetSide}-${positionGroup.position}`}
/>
</div>
))}
</div>
</div>
);
}

View File

@@ -60,30 +60,49 @@ export default function Home() {
});
return (
<div className="pt-5 px-10 grid grid-cols-12 gap-5">
<div className="col-span-2">
<div className="pt-2 md:pt-5 px-2 md:px-10 grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-5">
{/* Left sidebar - hidden on mobile */}
<div className="hidden md:block md:col-span-2">
<PosterVideoAdSides
side="left"
posterAds={data.filteredPosterAds}
videoAds={data.videoAds}
/>
</div>
<PosterAdCenterBottom
topAds={data.centerTopPosterAd}
bottomAds={data.centerBottomPosterAd}
>
<Suspense fallback={null}>
<LineAds />
</Suspense>
</PosterAdCenterBottom>
<div className="col-span-2">
{/* Main content - full width on mobile, 8 cols on desktop */}
<div className="col-span-1 md:col-span-8">
<PosterAdCenterBottom
topAds={data.centerTopPosterAd}
bottomAds={data.centerBottomPosterAd}
>
<Suspense fallback={null}>
<LineAds />
</Suspense>
</PosterAdCenterBottom>
</div>
{/* Right sidebar - hidden on mobile */}
<div className="hidden md:block md:col-span-2">
<PosterVideoAdSides
side="right"
posterAds={data.filteredPosterAds}
videoAds={data.videoAds}
/>
</div>
{/* Mobile-only: Side ads at bottom */}
<div className="md:hidden col-span-1 space-y-4">
{/* Left side ads on mobile */}
<div className="w-full">
<h3 className="text-lg font-semibold mb-2 text-center">Featured Ads</h3>
<PosterVideoAdSides
side="left"
posterAds={data.filteredPosterAds}
videoAds={data.videoAds}
/>
</div>
</div>
</div>
);
}

View File

@@ -240,68 +240,62 @@ export default function AdSlotsOverviewPage() {
}
return (
<div className="pt-5 px-10">
<div className="pt-4 px-4 md:pt-5 md:px-10">
{/* Summary Cards */}
{slotsData && (
<div className="grid grid-cols-12 gap-5 mb-8">
<div className="col-span-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Total Slots
</p>
<p className="text-2xl font-bold">{slotsData.totalSlots}</p>
</div>
<Layout className="h-8 w-8 text-blue-600" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-5 mb-6 md:mb-8">
<Card>
<CardContent className="p-4 md:p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs md:text-sm font-medium text-muted-foreground">
Total Slots
</p>
<p className="text-xl md:text-2xl font-bold">{slotsData.totalSlots}</p>
</div>
</CardContent>
</Card>
</div>
<div className="col-span-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Free Slots
</p>
<p className="text-2xl font-bold text-green-600">
{slotsData.freeSlots}
</p>
</div>
<div className="h-8 w-8 bg-green-500 rounded-full" />
<Layout className="h-6 w-6 md:h-8 md:w-8 text-blue-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 md:p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs md:text-sm font-medium text-muted-foreground">
Free Slots
</p>
<p className="text-xl md:text-2xl font-bold text-green-600">
{slotsData.freeSlots}
</p>
</div>
</CardContent>
</Card>
</div>
<div className="col-span-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Occupied Slots
</p>
<p className="text-2xl font-bold text-red-600">
{slotsData.occupiedSlots}
</p>
</div>
<div className="h-8 w-8 bg-red-500 rounded-full" />
<div className="h-6 w-6 md:h-8 md:w-8 bg-green-500 rounded-full" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 md:p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs md:text-sm font-medium text-muted-foreground">
Occupied Slots
</p>
<p className="text-xl md:text-2xl font-bold text-red-600">
{slotsData.occupiedSlots}
</p>
</div>
</CardContent>
</Card>
</div>
<div className="h-6 w-6 md:h-8 md:w-8 bg-red-500 rounded-full" />
</div>
</CardContent>
</Card>
</div>
)}
{/* Date Navigation and Filters */}
<div className="grid grid-cols-12 gap-5 mb-6">
<div className="col-span-4">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-5 mb-4 md:mb-6">
<div>
<div className="flex items-center space-x-2 mb-2">
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Date Navigation:</span>
<span className="text-xs md:text-sm font-medium">Date Navigation:</span>
</div>
<div className="flex items-center space-x-2">
<Button
@@ -312,11 +306,12 @@ export default function AdSlotsOverviewPage() {
!availableDates ||
availableDates.dates.indexOf(selectedDate) === 0
}
className="px-2 md:px-3"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Select value={selectedDate} onValueChange={setSelectedDate}>
<SelectTrigger className="flex-1">
<SelectTrigger className="flex-1 text-xs md:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -341,21 +336,22 @@ export default function AdSlotsOverviewPage() {
availableDates.dates.indexOf(selectedDate) ===
availableDates.dates.length - 1
}
className="px-2 md:px-3"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
<div className="col-span-4">
<div>
<div className="flex items-center space-x-2 mb-2">
<Filter className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Page Type:</span>
<span className="text-xs md:text-sm font-medium">Page Type:</span>
</div>
<Select
value={pageTypeFilter}
onValueChange={(value: PageTypeFilter) => setPageTypeFilter(value)}
>
<SelectTrigger>
<SelectTrigger className="text-xs md:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -364,17 +360,17 @@ export default function AdSlotsOverviewPage() {
</SelectContent>
</Select>
</div>
<div className="col-span-4">
<div>
<div className="flex items-center space-x-2 mb-2">
<Filter className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Category:</span>
<span className="text-xs md:text-sm font-medium">Category:</span>
</div>
<Select
value={selectedCategory}
onValueChange={setSelectedCategory}
disabled={pageTypeFilter !== "CATEGORY"}
>
<SelectTrigger>
<SelectTrigger className="text-xs md:text-sm">
<SelectValue placeholder="All Categories" />
</SelectTrigger>
<SelectContent>
@@ -390,33 +386,46 @@ export default function AdSlotsOverviewPage() {
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-12 gap-5">
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 md:gap-5">
{/* Left Column - Ad Slots */}
<div className="col-span-8">
<div className="xl:col-span-2">
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2">
<Grid className="h-5 w-5" />
<CardHeader className="p-4 md:p-6">
<CardTitle className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<span className="flex items-center gap-2 text-lg md:text-xl">
<Grid className="h-4 w-4 md:h-5 md:w-5" />
Ad Slots
</span>
<Badge variant="outline">
{selectedDate &&
new Date(selectedDate + "T00:00:00").toLocaleDateString(
"en-US",
{
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}
)}
<Badge variant="outline" className="text-xs w-fit">
<span className="hidden sm:inline">
{selectedDate &&
new Date(selectedDate + "T00:00:00").toLocaleDateString(
"en-US",
{
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}
)}
</span>
<span className="sm:hidden">
{selectedDate &&
new Date(selectedDate + "T00:00:00").toLocaleDateString(
"en-US",
{
weekday: "short",
month: "short",
day: "numeric",
}
)}
</span>
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="p-4 md:p-6">
{slotsData && slotsData.slots.length > 0 ? (
<div className="grid grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 md:gap-4">
{slotsData.slots.map((slot) => {
const status = getSlotStatus(slot);
return (
@@ -427,10 +436,10 @@ export default function AdSlotsOverviewPage() {
)}`}
onClick={() => handleSlotClick(slot)}
>
<CardContent className="p-4">
<div className="space-y-2">
<CardContent className="p-3 md:p-4">
<div className="space-y-2 md:space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">
<span className="text-sm md:text-sm font-medium">
Pos {slot.position}
</span>
<Badge variant="outline" className="text-xs">
@@ -439,35 +448,61 @@ export default function AdSlotsOverviewPage() {
</div>
<div className="text-xs text-muted-foreground">
{slot.pageType} - {slot.side?.replace("_", " ")}
<span className="hidden sm:inline">
{slot.pageType} - {slot.side?.replace("_", " ")}
</span>
<span className="sm:hidden">
{slot.pageType}
</span>
</div>
{slot.categories && slot.categories.length > 0 && (
<div className="flex flex-wrap gap-1">
{slot.categories.slice(0, 2).map((cat) => (
<Badge
key={cat.id}
className={`text-xs ${getCategoryBadgeColor(
cat
)}`}
>
{cat.name}
</Badge>
))}
{slot.categories.length > 2 && (
<Badge variant="outline" className="text-xs">
+{slot.categories.length - 2}
</Badge>
)}
{/* Mobile: Show only 1 category */}
<div className="sm:hidden flex flex-wrap gap-1">
{slot.categories.slice(0, 1).map((cat) => (
<Badge
key={cat.id}
className={`text-xs ${getCategoryBadgeColor(
cat
)}`}
>
{cat.name.length > 8 ? cat.name.substring(0, 6) + "..." : cat.name}
</Badge>
))}
{slot.categories.length > 1 && (
<Badge variant="outline" className="text-xs">
+{slot.categories.length - 1}
</Badge>
)}
</div>
{/* Desktop: Show up to 2 categories */}
<div className="hidden sm:flex flex-wrap gap-1">
{slot.categories.slice(0, 2).map((cat) => (
<Badge
key={cat.id}
className={`text-xs ${getCategoryBadgeColor(
cat
)}`}
>
{cat.name}
</Badge>
))}
{slot.categories.length > 2 && (
<Badge variant="outline" className="text-xs">
+{slot.categories.length - 2}
</Badge>
)}
</div>
</div>
)}
<div className="text-xs text-muted-foreground">
{slot.activeAdsCount}/{slot.maxCapacity} ads
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="w-full bg-gray-200 rounded-full h-1.5 md:h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${
className={`h-1.5 md:h-2 rounded-full transition-all duration-300 ${
status === "FREE"
? "bg-green-500"
: status === "PARTIALLY_OCCUPIED"
@@ -501,12 +536,12 @@ export default function AdSlotsOverviewPage() {
</div>
{/* Right Column - Line Ads */}
<div className="col-span-4">
<div className="xl:col-span-1">
<Card>
<CardHeader>
<CardTitle>Line Ads ({lineAdsData?.totalCount || 0})</CardTitle>
<CardHeader className="p-4 md:p-6">
<CardTitle className="text-lg md:text-xl">Line Ads ({lineAdsData?.totalCount || 0})</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="p-4 md:p-6">
{lineAdsLoading ? (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
@@ -514,7 +549,7 @@ export default function AdSlotsOverviewPage() {
))}
</div>
) : lineAdsData ? (
<div className="space-y-3">
<div className="space-y-2 md:space-y-3">
{[...lineAdsData.homeAds, ...lineAdsData.categoryAds]
.slice(0, 6)
.map((ad) => (
@@ -524,7 +559,7 @@ export default function AdSlotsOverviewPage() {
>
<CardContent className="p-3">
<div className="space-y-2">
<div className="font-medium text-sm line-clamp-1">
<div className="font-medium text-xs md:text-sm line-clamp-1">
{ad.title}
</div>
<div className="text-xs text-muted-foreground line-clamp-2">
@@ -537,12 +572,17 @@ export default function AdSlotsOverviewPage() {
ad.mainCategory
)}`}
>
{ad.mainCategory.name}
<span className="hidden sm:inline">{ad.mainCategory.name}</span>
<span className="sm:hidden">
{ad.mainCategory.name.length > 8
? ad.mainCategory.name.substring(0, 8) + "..."
: ad.mainCategory.name}
</span>
</Badge>
)}
<div className="flex items-center justify-between">
<Badge className={getStatusBadgeColor(ad.status)}>
<Badge className={`${getStatusBadgeColor(ad.status)} text-xs`}>
{ad.status}
</Badge>
<div className="text-xs text-muted-foreground">
@@ -568,12 +608,17 @@ export default function AdSlotsOverviewPage() {
{/* Slot Details Modal */}
<Dialog open={slotDetailsOpen} onOpenChange={setSlotDetailsOpen}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto mx-4 md:mx-auto">
<DialogHeader>
<DialogTitle>
Slot Details - {selectedSlot?.pageType} Page,{" "}
{selectedSlot?.side?.replace("_", " ")}, Position{" "}
{selectedSlot?.position}
<DialogTitle className="text-lg md:text-xl">
<span className="hidden md:inline">
Slot Details - {selectedSlot?.pageType} Page,{" "}
{selectedSlot?.side?.replace("_", " ")}, Position{" "}
{selectedSlot?.position}
</span>
<span className="md:hidden">
{selectedSlot?.pageType} - Pos {selectedSlot?.position}
</span>
</DialogTitle>
</DialogHeader>
@@ -584,19 +629,19 @@ export default function AdSlotsOverviewPage() {
))}
</div>
) : slotDetails ? (
<div className="space-y-6">
<div className="grid grid-cols-3 gap-4">
<div>
<p className="text-sm text-muted-foreground">
<div className="space-y-4 md:space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="text-center sm:text-left">
<p className="text-xs md:text-sm text-muted-foreground">
Current Occupancy
</p>
<p className="text-2xl font-bold">
<p className="text-xl md:text-2xl font-bold">
{slotDetails.currentOccupancy}/{slotDetails.maxCapacity}
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Status</p>
<Badge variant="outline">
<div className="text-center sm:text-left">
<p className="text-xs md:text-sm text-muted-foreground">Status</p>
<Badge variant="outline" className="text-xs">
{slotDetails.currentOccupancy === 0
? "FREE"
: slotDetails.currentOccupancy >= slotDetails.maxCapacity
@@ -604,22 +649,48 @@ export default function AdSlotsOverviewPage() {
: "PARTIALLY OCCUPIED"}
</Badge>
</div>
<div>
<p className="text-sm text-muted-foreground">Total Ads</p>
<p className="text-xl font-bold">{slotDetails.ads.length}</p>
<div className="text-center sm:text-left">
<p className="text-xs md:text-sm text-muted-foreground">Total Ads</p>
<p className="text-lg md:text-xl font-bold">{slotDetails.ads.length}</p>
</div>
</div>
<div className="space-y-4">
<h4 className="text-lg font-semibold">
<div className="space-y-3 md:space-y-4">
<h4 className="text-base md:text-lg font-semibold">
Active Ads in this Slot
</h4>
{slotDetails.ads.length > 0 ? (
<div className="space-y-3">
<div className="space-y-2 md:space-y-3">
{slotDetails.ads.map((ad, index) => (
<Card key={`${ad.id}-${index}`}>
<CardContent className="p-4">
<div className="grid grid-cols-5 gap-4 items-center">
<CardContent className="p-3 md:p-4">
{/* Mobile Layout */}
<div className="md:hidden space-y-2">
<div className="font-medium text-sm">{ad.title}</div>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs">{ad.adType}</Badge>
<Badge className={`${getStatusBadgeColor(ad.status)} text-xs`}>
{ad.status}
</Badge>
{ad.mainCategory && (
<Badge
className={`text-xs ${getCategoryBadgeColor(
ad.mainCategory
)}`}
>
{ad.mainCategory.name.length > 8
? ad.mainCategory.name.substring(0, 8) + "..."
: ad.mainCategory.name}
</Badge>
)}
</div>
<div className="text-xs text-muted-foreground">
{ad.isActive ? "Active" : "Inactive"}
</div>
</div>
{/* Desktop Layout */}
<div className="hidden md:grid grid-cols-5 gap-4 items-center">
<div className="col-span-2">
<span className="font-medium">{ad.title}</span>
{ad.mainCategory && (
@@ -651,8 +722,8 @@ export default function AdSlotsOverviewPage() {
))}
</div>
) : (
<div className="text-center py-8">
<p className="text-muted-foreground">
<div className="text-center py-6 md:py-8">
<p className="text-muted-foreground text-sm">
No ads in this slot on {selectedDate}
</p>
</div>
@@ -660,8 +731,8 @@ export default function AdSlotsOverviewPage() {
</div>
</div>
) : (
<div className="text-center py-8">
<p className="text-red-600">Failed to load slot details</p>
<div className="text-center py-6 md:py-8">
<p className="text-red-600 text-sm">Failed to load slot details</p>
</div>
)}
</DialogContent>
@@ -672,62 +743,54 @@ export default function AdSlotsOverviewPage() {
function AdSlotsOverviewSkeleton() {
return (
<div className="pt-5 px-10">
<div className="pt-4 px-4 md:pt-5 md:px-10">
{/* Summary Cards */}
<div className="grid grid-cols-12 gap-5 mb-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-5 mb-6 md:mb-8">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="col-span-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-6 w-12" />
</div>
<Skeleton className="h-8 w-8 rounded-full" />
<Card key={i}>
<CardContent className="p-4 md:p-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton className="h-3 md:h-4 w-16 md:w-20" />
<Skeleton className="h-5 md:h-6 w-10 md:w-12" />
</div>
</CardContent>
</Card>
</div>
<Skeleton className="h-6 w-6 md:h-8 md:w-8 rounded-full" />
</div>
</CardContent>
</Card>
))}
</div>
{/* Filters */}
<div className="grid grid-cols-12 gap-5 mb-6">
<div className="col-span-4">
<Skeleton className="h-4 w-20 mb-2" />
<Skeleton className="h-10 w-full" />
</div>
<div className="col-span-4">
<Skeleton className="h-4 w-20 mb-2" />
<Skeleton className="h-10 w-full" />
</div>
<div className="col-span-4">
<Skeleton className="h-4 w-20 mb-2" />
<Skeleton className="h-10 w-full" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-5 mb-4 md:mb-6">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i}>
<Skeleton className="h-3 md:h-4 w-16 md:w-20 mb-2" />
<Skeleton className="h-9 md:h-10 w-full" />
</div>
))}
</div>
{/* Main Content */}
<div className="grid grid-cols-12 gap-5">
<div className="col-span-8">
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 md:gap-5">
<div className="xl:col-span-2">
<Card>
<CardHeader>
<Skeleton className="h-6 w-32" />
<CardHeader className="p-4 md:p-6">
<Skeleton className="h-5 md:h-6 w-24 md:w-32" />
</CardHeader>
<CardContent>
<div className="grid grid-cols-4 gap-4">
<CardContent className="p-4 md:p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 md:gap-4">
{Array.from({ length: 8 }).map((_, i) => (
<Card key={i}>
<CardContent className="p-4">
<CardContent className="p-3 md:p-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Skeleton className="h-4 w-12" />
<Skeleton className="h-5 w-16" />
<Skeleton className="h-3 md:h-4 w-10 md:w-12" />
<Skeleton className="h-4 md:h-5 w-12 md:w-16" />
</div>
<Skeleton className="h-3 w-20" />
<Skeleton className="h-3 w-16" />
<Skeleton className="h-2 w-full rounded-full" />
<Skeleton className="h-3 w-16 md:w-20" />
<Skeleton className="h-3 w-12 md:w-16" />
<Skeleton className="h-1.5 md:h-2 w-full rounded-full" />
</div>
</CardContent>
</Card>
@@ -736,15 +799,15 @@ function AdSlotsOverviewSkeleton() {
</CardContent>
</Card>
</div>
<div className="col-span-4">
<div className="xl:col-span-1">
<Card>
<CardHeader>
<Skeleton className="h-6 w-32" />
<CardHeader className="p-4 md:p-6">
<Skeleton className="h-5 md:h-6 w-24 md:w-32" />
</CardHeader>
<CardContent>
<div className="space-y-3">
<CardContent className="p-4 md:p-6">
<div className="space-y-2 md:space-y-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="h-16 w-full" />
<Skeleton key={i} className="h-14 md:h-16 w-full" />
))}
</div>
</CardContent>

View File

@@ -1,600 +0,0 @@
"use client";
import { useState, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Skeleton } from "@/components/ui/skeleton";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Users, Save, Plus, Trash2, Eye, AlertCircle, Building, Target, Lightbulb, Award } from "lucide-react";
import { toast } from "sonner";
import { format } from "date-fns";
import api from "@/lib/api";
interface TeamMember {
name: string;
position: string;
bio: string;
imageUrl?: string;
socialLinks?: {
linkedin?: string;
twitter?: string;
email?: string;
};
}
interface Achievement {
title: string;
description: string;
date: string;
}
interface AboutUs {
_id?: string;
companyOverview: string;
mission: string;
vision: string;
values: string[];
history: string;
teamMembers: TeamMember[];
achievements: Achievement[];
contactInfo: {
email: string;
phone: string;
address: string;
};
isActive: boolean;
lastUpdated: Date;
updatedBy: string;
}
interface AboutUsForm {
companyOverview: string;
mission: string;
vision: string;
values: string[];
history: string;
teamMembers: TeamMember[];
achievements: Achievement[];
contactInfo: {
email: string;
phone: string;
address: string;
};
}
export default function AboutUsConfig() {
const [formData, setFormData] = useState<AboutUsForm>({
companyOverview: "",
mission: "",
vision: "",
values: [""],
history: "",
teamMembers: [],
achievements: [],
contactInfo: {
email: "",
phone: "",
address: ""
}
});
const [hasChanges, setHasChanges] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const queryClient = useQueryClient();
// Get current about us data
const { data: currentAboutUs, isLoading, error } = useQuery({
queryKey: ["about-us"],
queryFn: async () => {
const { data } = await api.get("/configurations/about-us");
return data as AboutUs;
}
});
// Update about us mutation
const updateAboutUsMutation = useMutation({
mutationFn: async (aboutUsData: AboutUsForm) => {
const { data } = await api.post("/configurations/about-us", aboutUsData);
return data;
},
onSuccess: () => {
toast.success("About Us page updated successfully");
queryClient.invalidateQueries({ queryKey: ["about-us"] });
setHasChanges(false);
},
onError: (error: any) => {
toast.error(error.response?.data?.message || "Failed to update About Us page");
}
});
// Update form when current data loads
useEffect(() => {
if (currentAboutUs) {
setFormData({
companyOverview: currentAboutUs.companyOverview,
mission: currentAboutUs.mission,
vision: currentAboutUs.vision,
values: currentAboutUs.values.length > 0 ? currentAboutUs.values : [""],
history: currentAboutUs.history,
teamMembers: currentAboutUs.teamMembers,
achievements: currentAboutUs.achievements,
contactInfo: currentAboutUs.contactInfo
});
setHasChanges(false);
}
}, [currentAboutUs]);
const handleFormChange = (field: string, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
setHasChanges(true);
};
const handleNestedChange = (field: string, nestedField: string, value: any) => {
setFormData(prev => ({
...prev,
[field]: {
// @ts-ignore
...prev[field as keyof AboutUsForm],
[nestedField]: value
}
}));
setHasChanges(true);
};
const handleArrayChange = (field: "values" | "teamMembers" | "achievements", index: number, value: any) => {
setFormData(prev => {
const newArray = [...prev[field]];
newArray[index] = value;
return { ...prev, [field]: newArray };
});
setHasChanges(true);
};
const addArrayItem = (field: "values" | "teamMembers" | "achievements") => {
setFormData(prev => {
let newItem;
switch (field) {
case "values":
newItem = "";
break;
case "teamMembers":
newItem = { name: "", position: "", bio: "", socialLinks: {} };
break;
case "achievements":
newItem = { title: "", description: "", date: "" };
break;
}
return { ...prev, [field]: [...prev[field], newItem] };
});
setHasChanges(true);
};
const removeArrayItem = (field: "values" | "teamMembers" | "achievements", index: number) => {
setFormData(prev => {
const newArray = prev[field].filter((_, i) => i !== index);
return { ...prev, [field]: newArray };
});
setHasChanges(true);
};
const handleSave = () => {
// Filter out empty values
const cleanedData = {
...formData,
values: formData.values.filter(value => value.trim() !== "")
};
updateAboutUsMutation.mutate(cleanedData);
};
if (error) {
return (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Failed to load About Us configuration. Please try again.
</AlertDescription>
</Alert>
);
}
return (
<div className="space-y-6">
{/* About Us Configuration */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
About Us Page Configuration
</CardTitle>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="space-y-4">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="h-20 w-full" />
))}
</div>
) : (
<div className="space-y-8">
{/* Company Overview */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<Building className="h-5 w-5" />
<h3 className="text-lg font-semibold">Company Overview</h3>
</div>
<Textarea
value={formData.companyOverview}
onChange={(e) => handleFormChange("companyOverview", e.target.value)}
placeholder="Describe your company's main purpose and what you do..."
rows={4}
maxLength={1000}
/>
<div className="text-xs text-muted-foreground">
{formData.companyOverview.length}/1000 characters
</div>
</div>
{/* Mission & Vision */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label className="flex items-center gap-2">
<Target className="h-4 w-4" />
Mission Statement
</Label>
<Textarea
value={formData.mission}
onChange={(e) => handleFormChange("mission", e.target.value)}
placeholder="What is your company's mission?"
rows={3}
maxLength={500}
/>
<div className="text-xs text-muted-foreground">
{formData.mission.length}/500 characters
</div>
</div>
<div className="space-y-2">
<Label className="flex items-center gap-2">
<Lightbulb className="h-4 w-4" />
Vision Statement
</Label>
<Textarea
value={formData.vision}
onChange={(e) => handleFormChange("vision", e.target.value)}
placeholder="What is your company's vision for the future?"
rows={3}
maxLength={500}
/>
<div className="text-xs text-muted-foreground">
{formData.vision.length}/500 characters
</div>
</div>
</div>
{/* Company Values */}
<div className="space-y-4">
<div className="flex items-center gap-2 justify-between">
<h3 className="text-lg font-semibold">Company Values</h3>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => addArrayItem("values")}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add Value
</Button>
</div>
<div className="space-y-2">
{formData.values.map((value, index) => (
<div key={index} className="flex items-center gap-2">
<Input
value={value}
onChange={(e) => handleArrayChange("values", index, e.target.value)}
placeholder={`Company value ${index + 1}`}
maxLength={100}
/>
{formData.values.length > 1 && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeArrayItem("values", index)}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
))}
</div>
</div>
{/* Company History */}
<div className="space-y-2">
<Label>Company History</Label>
<Textarea
value={formData.history}
onChange={(e) => handleFormChange("history", e.target.value)}
placeholder="Tell the story of how your company was founded and evolved..."
rows={4}
maxLength={1500}
/>
<div className="text-xs text-muted-foreground">
{formData.history.length}/1500 characters
</div>
</div>
{/* Team Members */}
<div className="space-y-4">
<div className="flex items-center gap-2 justify-between">
<h3 className="text-lg font-semibold">Team Members</h3>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => addArrayItem("teamMembers")}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add Team Member
</Button>
</div>
<div className="space-y-4">
{formData.teamMembers.map((member, index) => (
<Card key={index} className="p-4">
<div className="flex items-center justify-between mb-4">
<h4 className="font-medium">Team Member {index + 1}</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeArrayItem("teamMembers", index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input
value={member.name}
onChange={(e) => handleArrayChange("teamMembers", index, { ...member, name: e.target.value })}
placeholder="Full Name"
/>
<Input
value={member.position}
onChange={(e) => handleArrayChange("teamMembers", index, { ...member, position: e.target.value })}
placeholder="Job Title/Position"
/>
<Input
value={member.imageUrl || ""}
onChange={(e) => handleArrayChange("teamMembers", index, { ...member, imageUrl: e.target.value })}
placeholder="Profile Image URL (optional)"
className="md:col-span-2"
/>
<Textarea
value={member.bio}
onChange={(e) => handleArrayChange("teamMembers", index, { ...member, bio: e.target.value })}
placeholder="Brief biography..."
rows={2}
className="md:col-span-2"
/>
</div>
</Card>
))}
</div>
</div>
{/* Achievements */}
<div className="space-y-4">
<div className="flex items-center gap-2 justify-between">
<div className="flex items-center gap-2">
<Award className="h-5 w-5" />
<h3 className="text-lg font-semibold">Company Achievements</h3>
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => addArrayItem("achievements")}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add Achievement
</Button>
</div>
<div className="space-y-4">
{formData.achievements.map((achievement, index) => (
<Card key={index} className="p-4">
<div className="flex items-center justify-between mb-4">
<h4 className="font-medium">Achievement {index + 1}</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeArrayItem("achievements", index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Input
value={achievement.title}
onChange={(e) => handleArrayChange("achievements", index, { ...achievement, title: e.target.value })}
placeholder="Achievement Title"
className="md:col-span-2"
/>
<Input
value={achievement.date}
onChange={(e) => handleArrayChange("achievements", index, { ...achievement, date: e.target.value })}
placeholder="Date (e.g., 2024)"
/>
<Textarea
value={achievement.description}
onChange={(e) => handleArrayChange("achievements", index, { ...achievement, description: e.target.value })}
placeholder="Description of the achievement..."
rows={2}
className="md:col-span-3"
/>
</div>
</Card>
))}
</div>
</div>
{/* Contact Information */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">Contact Information</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label>Company Email</Label>
<Input
type="email"
value={formData.contactInfo.email}
onChange={(e) => handleNestedChange("contactInfo", "email", e.target.value)}
placeholder="company@example.com"
/>
</div>
<div className="space-y-2">
<Label>Phone Number</Label>
<Input
value={formData.contactInfo.phone}
onChange={(e) => handleNestedChange("contactInfo", "phone", e.target.value)}
placeholder="+1 (555) 123-4567"
/>
</div>
<div className="space-y-2">
<Label>Address</Label>
<Input
value={formData.contactInfo.address}
onChange={(e) => handleNestedChange("contactInfo", "address", e.target.value)}
placeholder="Company address"
/>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex justify-between items-center pt-6 border-t">
<Button
variant="outline"
onClick={() => setShowPreview(!showPreview)}
className="flex items-center gap-2"
>
<Eye className="h-4 w-4" />
{showPreview ? "Hide" : "Show"} Preview
</Button>
<div className="flex items-center gap-2">
{hasChanges && (
<span className="text-sm text-muted-foreground">
Unsaved changes
</span>
)}
<Button
onClick={handleSave}
disabled={!hasChanges || updateAboutUsMutation.isPending}
className="flex items-center gap-2"
>
<Save className="h-4 w-4" />
{updateAboutUsMutation.isPending ? "Saving..." : "Save Changes"}
</Button>
</div>
</div>
{/* Last Updated Info */}
{currentAboutUs && (
<div className="text-sm text-muted-foreground border-t pt-4">
<div className="flex items-center justify-between">
<span>
Last updated: {format(new Date(currentAboutUs.lastUpdated), "PPpp")}
</span>
<span>
Updated by: {currentAboutUs.updatedBy}
</span>
</div>
</div>
)}
</div>
)}
</CardContent>
</Card>
{/* Preview */}
{showPreview && (
<Card>
<CardHeader>
<CardTitle>About Us Page Preview</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-8">
{/* Company Overview */}
{formData.companyOverview && (
<div>
<h2 className="text-2xl font-bold mb-4">About Our Company</h2>
<p className="text-muted-foreground leading-relaxed">{formData.companyOverview}</p>
</div>
)}
{/* Mission & Vision */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{formData.mission && (
<div>
<h3 className="text-xl font-semibold mb-2 flex items-center gap-2">
<Target className="h-5 w-5" />
Our Mission
</h3>
<p className="text-muted-foreground">{formData.mission}</p>
</div>
)}
{formData.vision && (
<div>
<h3 className="text-xl font-semibold mb-2 flex items-center gap-2">
<Lightbulb className="h-5 w-5" />
Our Vision
</h3>
<p className="text-muted-foreground">{formData.vision}</p>
</div>
)}
</div>
{/* Values */}
{formData.values.filter(v => v.trim()).length > 0 && (
<div>
<h3 className="text-xl font-semibold mb-4">Our Values</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{formData.values.filter(v => v.trim()).map((value, index) => (
<div key={index} className="flex items-center gap-2">
<Badge variant="secondary">{value}</Badge>
</div>
))}
</div>
</div>
)}
{/* Team Members */}
{formData.teamMembers.length > 0 && (
<div>
<h3 className="text-xl font-semibold mb-4">Our Team</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{formData.teamMembers.map((member, index) => (
<Card key={index} className="p-4">
<div className="text-center">
<h4 className="font-semibold">{member.name}</h4>
<p className="text-sm text-muted-foreground mb-2">{member.position}</p>
<p className="text-xs">{member.bio}</p>
</div>
</Card>
))}
</div>
</div>
)}
</div>
</CardContent>
</Card>
)}
</div>
);
}

View File

@@ -107,7 +107,7 @@ export function AdminReports() {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
return (
<div className="space-y-6">
<div className="space-y-4 md:space-y-6">
{/* Filters */}
<Card>
<CardHeader>
@@ -119,8 +119,8 @@ export function AdminReports() {
Configure date ranges and admin selection for activity reports
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<CardContent className="p-4 md:p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 md:gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">Start Date</label>
<Popover>
@@ -216,7 +216,7 @@ export function AdminReports() {
</Card>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Actions</CardTitle>
@@ -300,7 +300,7 @@ export function AdminReports() {
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
{/* Admin Activity Timeline */}
<Card>
<CardHeader>
@@ -453,42 +453,42 @@ export function AdminReports() {
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<table className="w-full text-xs md:text-sm">
<thead>
<tr className="border-b">
<th className="text-left p-2">Admin</th>
<th className="text-left p-2">Total Actions</th>
<th className="text-left p-2">Approvals</th>
<th className="text-left p-2">Rejections</th>
<th className="text-left p-2">Holds</th>
<th className="text-left p-2">Reviews</th>
<th className="text-left p-2">Response Time</th>
<th className="text-left p-1 md:p-2">Admin</th>
<th className="text-left p-1 md:p-2">Actions</th>
<th className="text-left p-1 md:p-2 hidden sm:table-cell">Approvals</th>
<th className="text-left p-1 md:p-2 hidden sm:table-cell">Rejections</th>
<th className="text-left p-1 md:p-2 hidden md:table-cell">Holds</th>
<th className="text-left p-1 md:p-2 hidden md:table-cell">Reviews</th>
<th className="text-left p-1 md:p-2 hidden lg:table-cell">Response Time</th>
</tr>
</thead>
<tbody>
{userWiseData.map((admin: any, index: number) => (
<tr key={index} className="border-b">
<td className="p-2 font-medium">{admin.adminName}</td>
<td className="p-2">
<Badge variant="secondary">{admin.totalActions}</Badge>
<td className="p-1 md:p-2 font-medium">{admin.adminName}</td>
<td className="p-1 md:p-2">
<Badge variant="secondary" className="text-xs">{admin.totalActions}</Badge>
</td>
<td className="p-2">
<Badge variant="default" className="bg-green-100 text-green-800">
<td className="p-1 md:p-2 hidden sm:table-cell">
<Badge variant="default" className="bg-green-100 text-green-800 text-xs">
{admin.approvals}
</Badge>
</td>
<td className="p-2">
<Badge variant="default" className="bg-red-100 text-red-800">
<td className="p-1 md:p-2 hidden sm:table-cell">
<Badge variant="default" className="bg-red-100 text-red-800 text-xs">
{admin.rejections}
</Badge>
</td>
<td className="p-2">
<Badge variant="default" className="bg-yellow-100 text-yellow-800">
<td className="p-1 md:p-2 hidden md:table-cell">
<Badge variant="default" className="bg-yellow-100 text-yellow-800 text-xs">
{admin.holds}
</Badge>
</td>
<td className="p-2">{admin.reviews}</td>
<td className="p-2">
<td className="p-1 md:p-2 hidden md:table-cell">{admin.reviews}</td>
<td className="p-1 md:p-2 hidden lg:table-cell">
{admin.avgTimeToAction ? `${admin.avgTimeToAction}h` : "N/A"}
</td>
</tr>

View File

@@ -100,7 +100,7 @@ export function ListingReports() {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
return (
<div className="space-y-6">
<div className="space-y-4 md:space-y-6">
{/* Filters */}
<Card>
<CardHeader>
@@ -112,8 +112,8 @@ export function ListingReports() {
Configure date ranges for listing performance reports
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<CardContent className="p-4 md:p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">Start Date</label>
<Popover>
@@ -180,7 +180,7 @@ export function ListingReports() {
</Card>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Listings</CardTitle>
@@ -266,7 +266,7 @@ export function ListingReports() {
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
{/* Listings by Category */}
<Card>
<CardHeader>

View File

@@ -49,54 +49,59 @@ export default function ReportsPage() {
};
return (
<div className="space-y-6 p-6">
<div className="space-y-4 md:space-y-6 p-4 md:p-6">
{/* Header */}
<div className="mb-6">
<h1 className="text-3xl font-bold tracking-tight mb-2">📊 Comprehensive Reports</h1>
<p className="text-muted-foreground">
<div className="mb-4 md:mb-6">
<h1 className="text-2xl md:text-3xl font-bold tracking-tight mb-2">📊 Comprehensive Reports</h1>
<p className="text-sm md:text-base text-muted-foreground">
Advanced analytics and business intelligence for Paisa Ads platform
</p>
</div>
{/* Report Categories Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="filtered-ads" className="flex items-center gap-2">
<BarChart3 className="h-4 w-4" />
Filtered Ads
<TabsList className="grid w-full grid-cols-2 md:grid-cols-5 gap-1 md:gap-0 h-auto md:h-10">
<TabsTrigger value="filtered-ads" className="flex items-center gap-1 md:gap-2 text-xs md:text-sm p-2 md:p-3">
<BarChart3 className="h-3 w-3 md:h-4 md:w-4" />
<span className="hidden sm:inline">Filtered Ads</span>
<span className="sm:hidden">Ads</span>
</TabsTrigger>
<TabsTrigger value="user-reports" className="flex items-center gap-2">
<Users className="h-4 w-4" />
User Reports
<TabsTrigger value="user-reports" className="flex items-center gap-1 md:gap-2 text-xs md:text-sm p-2 md:p-3">
<Users className="h-3 w-3 md:h-4 md:w-4" />
<span className="hidden sm:inline">User Reports</span>
<span className="sm:hidden">Users</span>
</TabsTrigger>
<TabsTrigger value="admin-reports" className="flex items-center gap-2">
<UserCheck className="h-4 w-4" />
Admin Reports
<TabsTrigger value="admin-reports" className="flex items-center gap-1 md:gap-2 text-xs md:text-sm p-2 md:p-3">
<UserCheck className="h-3 w-3 md:h-4 md:w-4" />
<span className="hidden sm:inline">Admin Reports</span>
<span className="sm:hidden">Admin</span>
</TabsTrigger>
<TabsTrigger value="listing-reports" className="flex items-center gap-2">
<FileText className="h-4 w-4" />
Listing Reports
<TabsTrigger value="listing-reports" className="flex items-center gap-1 md:gap-2 text-xs md:text-sm p-2 md:p-3">
<FileText className="h-3 w-3 md:h-4 md:w-4" />
<span className="hidden sm:inline">Listing Reports</span>
<span className="sm:hidden">Listings</span>
</TabsTrigger>
<TabsTrigger value="payment-reports" className="flex items-center gap-2">
<CreditCard className="h-4 w-4" />
Payment Reports
<TabsTrigger value="payment-reports" className="flex items-center gap-1 md:gap-2 text-xs md:text-sm p-2 md:p-3">
<CreditCard className="h-3 w-3 md:h-4 md:w-4" />
<span className="hidden sm:inline">Payment Reports</span>
<span className="sm:hidden">Payment</span>
</TabsTrigger>
</TabsList>
{/* Filtered Ads Tab (Original Functionality) */}
<TabsContent value="filtered-ads" className="space-y-6">
<TabsContent value="filtered-ads" className="space-y-4 md:space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
<CardHeader className="p-4 md:p-6">
<CardTitle className="flex items-center gap-2 text-lg md:text-xl">
<BarChart3 className="h-4 w-4 md:h-5 md:w-5" />
Filtered Ads Report
</CardTitle>
<CardDescription>
<CardDescription className="text-sm">
Filter and analyze ads data based on various criteria
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
<CardContent className="p-4 md:p-6">
<div className="space-y-4 md:space-y-6">
<SimpleReportsFilter
filters={filters}
onFiltersChange={handleFiltersChange}
@@ -112,22 +117,22 @@ export default function ReportsPage() {
</TabsContent>
{/* User Reports Tab */}
<TabsContent value="user-reports" className="space-y-6">
<TabsContent value="user-reports" className="space-y-4 md:space-y-6">
<UserReports />
</TabsContent>
{/* Admin Reports Tab */}
<TabsContent value="admin-reports" className="space-y-6">
<TabsContent value="admin-reports" className="space-y-4 md:space-y-6">
<AdminReports />
</TabsContent>
{/* Listing Reports Tab */}
<TabsContent value="listing-reports" className="space-y-6">
<TabsContent value="listing-reports" className="space-y-4 md:space-y-6">
<ListingReports />
</TabsContent>
{/* Payment Reports Tab */}
<TabsContent value="payment-reports" className="space-y-6">
<TabsContent value="payment-reports" className="space-y-4 md:space-y-6">
<PaymentReports />
</TabsContent>
</Tabs>

View File

@@ -102,7 +102,7 @@ export function PaymentReports() {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
return (
<div className="space-y-6">
<div className="space-y-4 md:space-y-6">
{/* Filters */}
<Card>
<CardHeader>
@@ -114,8 +114,8 @@ export function PaymentReports() {
Configure date ranges and grouping for payment reports
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<CardContent className="p-4 md:p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">Start Date</label>
<Popover>
@@ -195,7 +195,7 @@ export function PaymentReports() {
</Card>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
@@ -279,7 +279,7 @@ export function PaymentReports() {
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
{/* Revenue Timeline */}
<Card>
<CardHeader>

View File

@@ -102,11 +102,11 @@ export function SimpleReportsFilter({
}).length;
return (
<Card className="p-6">
<Card className="p-4 md:p-6">
<CardHeader className="p-0 mb-4">
<div className="flex items-center justify-between mb-4">
<CardTitle className="flex items-center gap-2 text-xl font-semibold">
<FilterIcon className="h-5 w-5" />
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-4 gap-2">
<CardTitle className="flex items-center gap-2 text-lg md:text-xl font-semibold">
<FilterIcon className="h-4 w-4 md:h-5 md:w-5" />
Filters
{activeFiltersCount > 0 && (
<span className="bg-primary text-primary-foreground text-xs px-2 py-1 rounded-full">
@@ -119,7 +119,7 @@ export function SimpleReportsFilter({
variant="outline"
size="sm"
onClick={clearAllFilters}
className="flex items-center gap-2"
className="flex items-center gap-2 w-fit"
>
<XIcon className="h-4 w-4" />
Clear All
@@ -128,9 +128,9 @@ export function SimpleReportsFilter({
</div>
</CardHeader>
<CardContent className="p-0">
<div className="grid gap-6">
<div className="grid gap-4 md:gap-6">
{/* Row 1: Ad Type, Status, and Location */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<div className="space-y-2">
<Label htmlFor="adType">Ad Type</Label>
<Select
@@ -215,7 +215,7 @@ export function SimpleReportsFilter({
</div>
{/* Row 2: User Type and Categories */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 md:gap-4">
<div className="space-y-2">
<Label htmlFor="userType">User Type</Label>
<Select
@@ -345,7 +345,7 @@ export function SimpleReportsFilter({
</div>
{/* Row 3: Date Range */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
<div className="space-y-2">
<Label>Start Date</Label>
<Popover>

View File

@@ -201,28 +201,29 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
}
return (
<div className="space-y-6 p-6">
<div className="space-y-4 md:space-y-6 p-0">
{/* Results Table */}
<Card className="shadow-sm">
<CardHeader className="p-6">
<CardTitle className="text-2xl font-bold">Results ({data.total})</CardTitle>
<CardDescription className="text-muted-foreground">
<CardHeader className="p-4 md:p-6">
<CardTitle className="text-lg md:text-2xl font-bold">Results ({data.total})</CardTitle>
<CardDescription className="text-sm md:text-base text-muted-foreground">
Page {currentPage} of {data.totalPages} {data.total} total results
</CardDescription>
</CardHeader>
<CardContent className="p-0">
<div className="rounded-md border">
{/* Desktop Table View */}
<div className="hidden md:block rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="p-4">Seq #</TableHead>
<TableHead className="p-4">Content</TableHead>
<TableHead className="p-4">Status</TableHead>
<TableHead className="p-4">Categories</TableHead>
<TableHead className="p-4">Location</TableHead>
<TableHead className="p-4">Customer</TableHead>
<TableHead className="p-4">Created</TableHead>
<TableHead className="p-4">Actions</TableHead>
<TableHead className="p-3 lg:p-4">Seq #</TableHead>
<TableHead className="p-3 lg:p-4">Content</TableHead>
<TableHead className="p-3 lg:p-4">Status</TableHead>
<TableHead className="p-3 lg:p-4">Categories</TableHead>
<TableHead className="p-3 lg:p-4">Location</TableHead>
<TableHead className="p-3 lg:p-4">Customer</TableHead>
<TableHead className="p-3 lg:p-4">Created</TableHead>
<TableHead className="p-3 lg:p-4">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -231,13 +232,13 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
return (
<TableRow key={ad.id}>
<TableCell className="font-medium p-4">
<TableCell className="font-medium p-3 lg:p-4">
{ad.sequenceNumber}
</TableCell>
<TableCell className="p-4">
<div className="flex items-center gap-3 max-w-xs">
<TableCell className="p-3 lg:p-4">
<div className="flex items-center gap-2 lg:gap-3 max-w-xs">
{image && (
<div className="relative h-12 w-12 overflow-hidden rounded-md flex-shrink-0">
<div className="relative h-10 w-10 lg:h-12 lg:w-12 overflow-hidden rounded-md flex-shrink-0">
<Image
src={`/api/images?imageName=${image.fileName}`}
alt="Ad image"
@@ -246,19 +247,19 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
/>
</div>
)}
<span className="line-clamp-2 text-sm">
<span className="line-clamp-2 text-xs lg:text-sm">
{getAdContent(ad)}
</span>
</div>
</TableCell>
<TableCell className="p-4">
<Badge variant={getStatusVariant(ad.status) as any}>
<TableCell className="p-3 lg:p-4">
<Badge variant={getStatusVariant(ad.status) as any} className="text-xs">
{ad.status.replace(/_/g, " ")}
</Badge>
</TableCell>
<TableCell className="p-4">
<TableCell className="p-3 lg:p-4">
<div className="space-y-1 max-w-xs">
<div className="text-sm font-medium">{ad.mainCategory?.name}</div>
<div className="text-xs lg:text-sm font-medium">{ad.mainCategory?.name}</div>
{(ad.categoryOne?.name || ad.categoryTwo?.name || ad.categoryThree?.name) && (
<div className="text-xs text-muted-foreground">
{[ad.categoryOne?.name, ad.categoryTwo?.name, ad.categoryThree?.name]
@@ -268,32 +269,32 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
)}
</div>
</TableCell>
<TableCell className="p-4">
<div className="text-sm">
<TableCell className="p-3 lg:p-4">
<div className="text-xs lg:text-sm">
<div>{ad.city || "N/A"}</div>
<div className="text-xs text-muted-foreground">{ad.state || "N/A"}</div>
</div>
</TableCell>
<TableCell className="p-4">
<div className="text-sm max-w-xs">
<TableCell className="p-3 lg:p-4">
<div className="text-xs lg:text-sm max-w-xs">
<div className="font-medium">{ad.customer?.user?.name || "Unknown"}</div>
<div className="text-xs text-muted-foreground truncate">
{ad.customer?.user?.email || ""}
</div>
</div>
</TableCell>
<TableCell className="p-4">
<div className="text-sm">
<TableCell className="p-3 lg:p-4">
<div className="text-xs lg:text-sm">
{format(new Date(ad.created_at), "MMM dd, yyyy")}
<div className="text-xs text-muted-foreground">
{format(new Date(ad.created_at), "hh:mm a")}
</div>
</div>
</TableCell>
<TableCell className="p-4">
<TableCell className="p-3 lg:p-4">
<Link href={getViewLink(ad)}>
<Button variant="outline" size="sm">
<EyeIcon className="mr-2 h-4 w-4" />
<Button variant="outline" size="sm" className="text-xs">
<EyeIcon className="mr-1 lg:mr-2 h-3 w-3 lg:h-4 lg:w-4" />
View
</Button>
</Link>
@@ -304,23 +305,102 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
</TableBody>
</Table>
</div>
{/* Mobile Card View */}
<div className="md:hidden space-y-4 p-4">
{data.items.map((ad: any) => {
const image = getAdImage(ad);
return (
<Card key={ad.id} className="p-4">
<div className="space-y-3">
{/* Header with sequence number and status */}
<div className="flex items-center justify-between">
<span className="font-medium text-sm">#{ad.sequenceNumber}</span>
<Badge variant={getStatusVariant(ad.status) as any} className="text-xs">
{ad.status.replace(/_/g, " ")}
</Badge>
</div>
{/* Content with image */}
<div className="flex items-start gap-3">
{image && (
<div className="relative h-16 w-16 overflow-hidden rounded-md flex-shrink-0">
<Image
src={`/api/images?imageName=${image.fileName}`}
alt="Ad image"
fill
className="object-cover"
/>
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-sm line-clamp-3">{getAdContent(ad)}</p>
</div>
</div>
{/* Categories */}
<div>
<p className="text-sm font-medium">{ad.mainCategory?.name}</p>
{(ad.categoryOne?.name || ad.categoryTwo?.name || ad.categoryThree?.name) && (
<p className="text-xs text-muted-foreground">
{[ad.categoryOne?.name, ad.categoryTwo?.name, ad.categoryThree?.name]
.filter(Boolean)
.join(" → ")}
</p>
)}
</div>
{/* Location and Customer */}
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<p className="font-medium">Location</p>
<p className="text-xs text-muted-foreground">
{ad.city || "N/A"}, {ad.state || "N/A"}
</p>
</div>
<div>
<p className="font-medium">Customer</p>
<p className="text-xs text-muted-foreground truncate">
{ad.customer?.user?.name || "Unknown"}
</p>
</div>
</div>
{/* Date and Action */}
<div className="flex items-center justify-between">
<div className="text-xs text-muted-foreground">
{format(new Date(ad.created_at), "MMM dd, yyyy")}
</div>
<Link href={getViewLink(ad)}>
<Button variant="outline" size="sm" className="text-xs">
<EyeIcon className="mr-1 h-3 w-3" />
View
</Button>
</Link>
</div>
</div>
</Card>
);
})}
</div>
</CardContent>
</Card>
{/* Simple Pagination */}
{data.totalPages > 1 && (
<div className="flex items-center justify-between mt-6">
<div className="flex flex-col sm:flex-row items-center justify-between mt-4 md:mt-6 gap-2 px-4 md:px-0">
<Button
variant="outline"
onClick={handlePreviousPage}
disabled={currentPage <= 1}
className="flex items-center gap-2 px-4 py-2"
className="flex items-center gap-2 px-3 md:px-4 py-2 text-sm w-full sm:w-auto"
>
<ChevronLeftIcon className="h-4 w-4" />
Previous
</Button>
<div className="text-sm text-muted-foreground">
<div className="text-sm text-muted-foreground order-first sm:order-none">
Page {currentPage} of {data.totalPages}
</div>
@@ -328,7 +408,7 @@ export function SimpleReportsTable({ filters, currentPage, onPageChange }: Simpl
variant="outline"
onClick={handleNextPage}
disabled={currentPage >= data.totalPages}
className="flex items-center gap-2 px-4 py-2"
className="flex items-center gap-2 px-3 md:px-4 py-2 text-sm w-full sm:w-auto"
>
Next
<ChevronRightIcon className="h-4 w-4" />

View File

@@ -110,7 +110,7 @@ export function UserReports() {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
return (
<div className="space-y-6">
<div className="space-y-4 md:space-y-6">
{/* Filters */}
<Card>
<CardHeader>
@@ -122,8 +122,8 @@ export function UserReports() {
Configure date ranges and grouping for user reports
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<CardContent className="p-4 md:p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">Start Date</label>
<Popover>
@@ -203,7 +203,7 @@ export function UserReports() {
</Card>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Users</CardTitle>
@@ -287,7 +287,7 @@ export function UserReports() {
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
{/* User Registration Trends */}
<Card>
<CardHeader>
@@ -430,32 +430,32 @@ export function UserReports() {
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<table className="w-full text-xs md:text-sm">
<thead>
<tr className="border-b">
<th className="text-left p-2">Period</th>
<th className="text-left p-2">Total</th>
<th className="text-left p-2">Active</th>
<th className="text-left p-2">By Role</th>
<th className="text-left p-2">By Gender</th>
<th className="text-left p-1 md:p-2">Period</th>
<th className="text-left p-1 md:p-2">Total</th>
<th className="text-left p-1 md:p-2">Active</th>
<th className="text-left p-1 md:p-2 hidden sm:table-cell">By Role</th>
<th className="text-left p-1 md:p-2 hidden sm:table-cell">By Gender</th>
</tr>
</thead>
<tbody>
{registrationData.data.map((item: any, index: number) => (
<tr key={index} className="border-b">
<td className="p-2 font-medium">{item.period}</td>
<td className="p-2">{item.count}</td>
<td className="p-2">
<Badge variant="secondary">{item.activeCount}</Badge>
<td className="p-1 md:p-2 font-medium">{item.period}</td>
<td className="p-1 md:p-2">{item.count}</td>
<td className="p-1 md:p-2">
<Badge variant="secondary" className="text-xs">{item.activeCount}</Badge>
</td>
<td className="p-2">
<td className="p-1 md:p-2 hidden sm:table-cell">
<div className="text-xs space-y-1">
{item.byRole && Object.entries(item.byRole).map(([role, count]: [string, any]) => (
<div key={role}>{role}: {count}</div>
))}
</div>
</td>
<td className="p-2">
<td className="p-1 md:p-2 hidden sm:table-cell">
<div className="text-xs space-y-1">
{item.byGender && Object.entries(item.byGender).map(([gender, count]: [string, any]) => (
<div key={gender}>{gender}: {count}</div>

View File

@@ -91,7 +91,7 @@ export default function VerifyOtpPage() {
}
// Show verification form only if user exists and is not verified
if (!user || user.phone_verified || error) {
if ( user?.phone_verified || error) {
return null;
}
@@ -114,7 +114,7 @@ export default function VerifyOtpPage() {
{/* Main content */}
<div className="flex items-center justify-center min-h-[calc(100vh-80px)] p-4">
<PhoneVerification
phoneNumber={user.phone_number}
phoneNumber={user?.phone_number ?? '0'}
onVerificationSuccess={handleVerificationSuccess}
onBack={handleBack}
/>

View File

@@ -146,11 +146,7 @@ export default function OtpViewerLogin({
return (
<div className="space-y-4">
<div className="text-center">
<Phone className="mx-auto h-12 w-12 text-primary mb-4" />
<h3 className="text-lg font-semibold mb-2">Quick Login with Phone</h3>
<p className="text-sm text-muted-foreground mb-4">
Enter your phone number to receive an OTP and access advertisements
</p>
{/* <h3 className="text-lg font-semibold mb-2">Quick Login with Phone</h3> */}
</div>
<Form {...phoneForm}>
@@ -209,7 +205,6 @@ export default function OtpViewerLogin({
return (
<div className="space-y-4">
<div className="text-center">
<Shield className="mx-auto h-12 w-12 text-primary mb-4" />
<h3 className="text-lg font-semibold mb-2">Enter OTP</h3>
<p className="text-sm text-muted-foreground mb-4">
We've sent a 6-digit code to +91 {phoneNumber}
@@ -221,7 +216,6 @@ export default function OtpViewerLogin({
<div className="space-y-2">
<label className="text-sm font-medium">Verification Code</label>
<Input
placeholder="123456"
maxLength={6}
className="text-center text-xl tracking-widest"
value={otpValue}

View File

@@ -152,9 +152,6 @@ export default function PhoneVerification({
<Shield className="h-10 w-10 text-blue-600" />
</div>
<CardTitle>Verify Your Phone Number</CardTitle>
<CardDescription>
We need to verify your phone number before you can access your dashboard
</CardDescription>
</CardHeader>
<CardContent>
<Form {...sendOtpForm}>

View File

@@ -147,7 +147,7 @@ export default function RegisterForm() {
onError: (error) => {
console.error("Registration error:", error);
toast.error("Registration failed", {
description: "Please check your information and try again",
description: "Phone number or email address already in use.",
});
},
});

View File

@@ -12,18 +12,20 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { LogOut, User, Settings } from "lucide-react";
import { LogOut, User, Settings, Menu } from "lucide-react";
import { toast } from "sonner";
import { logoutFromServer } from "@/logout";
interface ManagementHeaderProps {
userName: string;
userEmail: string;
onToggleSidebar?: () => void;
}
export function ManagementHeader({
userName,
userEmail,
onToggleSidebar,
}: ManagementHeaderProps) {
const router = useRouter();
const [isLoggingOut, setIsLoggingOut] = useState(false);
@@ -50,10 +52,21 @@ export function ManagementHeader({
};
return (
<header className="bg-white border-b px-6 py-3">
<header className="bg-white border-b px-4 md:px-6 py-3">
<div className="flex items-center justify-between">
{/* <h1 className="text-xl font-semibold">Management Dashboard</h1> */}
<div></div>
<div className="flex items-center">
<Button
variant="ghost"
size="icon"
className="md:hidden mr-2"
onClick={onToggleSidebar}
>
<Menu className="h-5 w-5" />
</Button>
<h1 className="text-lg md:text-xl font-semibold md:hidden">
PaisaAds
</h1>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-10 w-10 rounded-full">

View File

@@ -16,6 +16,7 @@ interface ManagementLayoutProps {
}
export function ManagementLayout({ children }: ManagementLayoutProps) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { data } = useQuery<User>({
queryKey: ["user"],
queryFn: async () => {
@@ -25,6 +26,9 @@ export function ManagementLayout({ children }: ManagementLayoutProps) {
});
const pathname = usePathname();
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
const closeSidebar = () => setIsSidebarOpen(false);
if (!data) {
return (
<div className="flex items-center justify-center h-screen">
@@ -35,10 +39,19 @@ export function ManagementLayout({ children }: ManagementLayoutProps) {
return (
<div className="flex h-screen bg-gray-100">
<ManagementSidebar userRole={data.role} pathname={pathname} />
<div className="flex flex-col flex-1 overflow-hidden">
<ManagementHeader userName={data.name} userEmail={data.email} />
<main className="flex-1 overflow-y-auto p-6 bg-gray-50">
<ManagementSidebar
userRole={data.role}
pathname={pathname}
isOpen={isSidebarOpen}
onClose={closeSidebar}
/>
<div className="flex flex-col flex-1 overflow-hidden md:ml-0">
<ManagementHeader
userName={data.name}
userEmail={data.email}
onToggleSidebar={toggleSidebar}
/>
<main className="flex-1 overflow-y-auto p-4 md:p-6 bg-gray-50">
{children}
</main>
</div>

View File

@@ -27,6 +27,8 @@ import { useState } from "react";
interface ManagementSidebarProps {
userRole: Role;
pathname: string;
isOpen?: boolean;
onClose?: () => void;
}
interface MenuItem {
@@ -43,6 +45,8 @@ interface MenuItem {
export function ManagementSidebar({
userRole,
pathname,
isOpen = false,
onClose,
}: ManagementSidebarProps) {
const [openSubmenu, setOpenSubmenu] = useState<string | null>(null);
@@ -182,12 +186,24 @@ export function ManagementSidebar({
};
return (
<div className="w-64 bg-white border-r shadow-sm overflow-y-auto">
<div className="p-4 border-b">
<Link href="/mgmt/dashboard" className="flex items-center">
<span className="text-xl font-bold text-primary">PaisaAds</span>
</Link>
</div>
<>
{/* Mobile overlay */}
{isOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"
onClick={onClose}
/>
)}
<div className={cn(
"w-64 bg-white border-r shadow-sm overflow-y-auto fixed left-0 top-0 h-full z-50 transform transition-transform duration-300 ease-in-out md:relative md:translate-x-0",
isOpen ? "translate-x-0" : "-translate-x-full"
)}>
<div className="p-4 border-b">
<Link href="/mgmt/dashboard" className="flex items-center">
<span className="text-xl font-bold text-primary">PaisaAds</span>
</Link>
</div>
<nav className="p-2">
<ul className="space-y-1">
{menuItems
@@ -222,6 +238,7 @@ export function ManagementSidebar({
<li key={subitem.title}>
<Link
href={subitem.href}
onClick={onClose}
className={cn(
"flex items-center px-3 py-2 text-sm rounded-md hover:bg-gray-100",
isActive(subitem.href) &&
@@ -247,6 +264,7 @@ export function ManagementSidebar({
) : (
<Link
href={item.href}
onClick={onClose}
className={cn(
"flex items-center px-3 py-2 text-sm rounded-md hover:bg-gray-100",
active && "bg-gray-100 font-medium"
@@ -262,5 +280,6 @@ export function ManagementSidebar({
</ul>
</nav>
</div>
</>
);
}

View File

@@ -112,7 +112,7 @@ export default function Navbar() {
{[
{ href: "/", label: "Home" },
{ href: "/about-us", label: "About" },
{ href: "/contact-us", label: "Contact" },
{ href: "/contact", label: "Contact" },
{ href: "/faq", label: "FAQ" },
].map((item) => (
<Link
@@ -232,27 +232,15 @@ export default function Navbar() {
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>
Quick Access - View Advertisements
View Advertisements
</DialogTitle>
</DialogHeader>
<OtpViewerLogin
onSuccess={() => setIsViewAdOpen(false)}
onSuccess={() => {
setIsViewAdOpen(false)
router.push('/search')
}}
/>
<div className="mt-4 text-center">
<span className="text-sm text-muted-foreground">
Don't have an account?{" "}
</span>
<Button
variant="link"
className="p-0 h-auto text-sm"
onClick={() => {
setIsViewAdOpen(false);
router.push("/register");
}}
>
Create an account
</Button>
</div>
</DialogContent>
</Dialog>

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB