Compare commits
2 Commits
b92128c825
...
8425397439
| Author | SHA1 | Date | |
|---|---|---|---|
| 8425397439 | |||
| acbd3c8421 |
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||
import AdPricingConfig from "./ad-pricing/page";
|
||||
import PrivacyPolicyConfig from "./privacy-policy/page";
|
||||
import SearchSloganConfig from "./search-slogan/page";
|
||||
import AboutUsConfig from "./about-us/page";
|
||||
import FaqConfig from "./faq/page";
|
||||
import ContactPageConfig from "./contact-page/page";
|
||||
import TermsConditionsConfig from "./tc/page";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
BIN
{67D132E1-DA8C-4BB2-88D7-E8825BAE60CB}.png
Normal file
BIN
{67D132E1-DA8C-4BB2-88D7-E8825BAE60CB}.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 402 KiB |
Reference in New Issue
Block a user