Add src/app/reviews/page.tsx
This commit is contained in:
190
src/app/reviews/page.tsx
Normal file
190
src/app/reviews/page.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
"use client";
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { ThemeProvider } from '@/components/ThemeProvider';
|
||||
import NavbarStyleApple from '@/components/navbar/NavbarStyleApple/NavbarStyleApple';
|
||||
import TestimonialCardThirteen from '@/components/sections/testimonial/TestimonialCardThirteen';
|
||||
import Input from '@/components/form/Input';
|
||||
import ButtonTextUnderline from '@/components/button/ButtonTextUnderline';
|
||||
|
||||
// Dummy data generation
|
||||
const generateReviews = (count: number) => {
|
||||
const reviews = [];
|
||||
const names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Heidi", "Ivan", "Judy"];
|
||||
const reviewTexts = [
|
||||
"Absolutely love this product! It exceeded my expectations.", "Great quality and very fast shipping. Highly recommend!", "Decent product for the price. Would buy again.", "Not entirely satisfied, but it gets the job done.", "Fantastic experience from start to finish. Five stars!", "The item arrived damaged. Customer service was helpful.", "Works as advertised. No complaints here.", "A bit pricey, but the quality justifies it.", "I've been looking for something like this for ages!", "Could be better, but it's okay.", "Exceptional value and brilliant design. This product truly stands out!", "I was skeptical at first, but now I'm a believer. What an amazing item.", "The best purchase I've made this year. So glad I found it!", "Slight learning curve, but once you get it, it's incredibly powerful.", "My new go-to. It's so versatile and performs perfectly every time.", "Customer support was stellar when I had a question. Very impressed.", "Perfect for daily use, durable, and aesthetically pleasing. A solid 10/10.", "Initially had an issue, but it was resolved quickly and efficiently. Good job.", "Transforms my routine. I can't imagine going back to how things were before.", "A genuinely innovative product that solves a real problem effortlessly.", "Surprisingly robust given its compact size. It handles everything with ease.", "I've recommended this to all my friends. It's that good!", "The attention to detail is evident. Every aspect feels thoughtfully designed.", "Worth every penny. The performance is consistently high.", "Exceeded my expectations in terms of functionality and ease of use."
|
||||
];
|
||||
const imagePlaceholders = [
|
||||
"https://api.dicebear.com/7.x/pixel-art/svg?seed=1", "https://api.dicebear.com/7.x/pixel-art/svg?seed=2", "https://api.dicebear.com/7.x/pixel-art/svg?seed=3", "https://api.dicebear.com/7.x/pixel-art/svg?seed=4", "https://api.dicebear.com/7.x/pixel-art/svg?seed=5", "https://api.dicebear.com/7.x/pixel-art/svg?seed=6", "https://api.dicebear.com/7.x/pixel-art/svg?seed=7", "https://api.dicebear.com/7.x/pixel-art/svg?seed=8", "https://api.dicebear.com/7.x/pixel-art/svg?seed=9", "https://api.dicebear.com/7.x/pixel-art/svg?seed=10"
|
||||
];
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const name = names[Math.floor(Math.random() * names.length)];
|
||||
const rating = Math.floor(Math.random() * 5) + 1; // 1-5 stars
|
||||
const reviewText = reviewTexts[Math.floor(Math.random() * reviewTexts.length)];
|
||||
const date = new Date(Date.now() - Math.floor(Math.random() * 365 * 24 * 60 * 60 * 1000)).toISOString().split('T')[0];
|
||||
const helpfulVotes = Math.floor(Math.random() * 100);
|
||||
const isVerifiedPurchase = Math.random() > 0.3; // 70% verified
|
||||
const imageSrc = imagePlaceholders[Math.floor(Math.random() * imagePlaceholders.length)];
|
||||
|
||||
reviews.push({
|
||||
id: `review-${i}`,
|
||||
name,
|
||||
rating,
|
||||
reviewText,
|
||||
date,
|
||||
helpfulVotes,
|
||||
isVerifiedPurchase,
|
||||
imageSrc,
|
||||
});
|
||||
}
|
||||
return reviews;
|
||||
};
|
||||
|
||||
const ALL_REVIEWS = generateReviews(350); // Generate 350 reviews
|
||||
|
||||
export default function ReviewsPage() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortCriteria, setSortCriteria] = useState('newest'); // 'newest', 'oldest', 'highestRating', 'lowestRating', 'mostHelpful'
|
||||
const [reviewsPerPage, setReviewsPerPage] = useState(20); // Changed from 10 to 20 for more content per load
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const filteredAndSortedReviews = useMemo(() => {
|
||||
let reviews = [...ALL_REVIEWS];
|
||||
|
||||
// 1. Filter
|
||||
if (searchTerm) {
|
||||
reviews = reviews.filter(review =>
|
||||
review.reviewText.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
review.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Sort
|
||||
reviews.sort((a, b) => {
|
||||
switch (sortCriteria) {
|
||||
case 'newest':
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||
case 'oldest':
|
||||
return new Date(a.date).getTime() - new Date(b.date).getTime();
|
||||
case 'highestRating':
|
||||
return b.rating - a.rating;
|
||||
case 'lowestRating':
|
||||
return a.rating - b.rating;
|
||||
case 'mostHelpful':
|
||||
return b.helpfulVotes - a.helpfulVotes;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return reviews;
|
||||
}, [searchTerm, sortCriteria]);
|
||||
|
||||
const displayedReviews = useMemo(() => {
|
||||
const startIndex = 0;
|
||||
const endIndex = currentPage * reviewsPerPage;
|
||||
return filteredAndSortedReviews.slice(startIndex, endIndex);
|
||||
}, [filteredAndSortedReviews, currentPage, reviewsPerPage]);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
setCurrentPage(prevPage => prevPage + 1);
|
||||
};
|
||||
|
||||
const hasMoreReviews = displayedReviews.length < filteredAndSortedReviews.length;
|
||||
|
||||
const testimonialCardData = displayedReviews.map(review => ({
|
||||
id: review.id,
|
||||
name: review.name,
|
||||
handle: review.isVerifiedPurchase ? "Verified Purchase" : "Customer", testimonial: `${review.reviewText} (Reviewed on ${review.date}). Found helpful by ${review.helpfulVotes} people.`,
|
||||
rating: review.rating,
|
||||
imageSrc: review.imageSrc,
|
||||
imageAlt: `${review.name}'s avatar`,
|
||||
}));
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="hover-magnetic"
|
||||
defaultTextAnimation="entrance-slide"
|
||||
borderRadius="rounded"
|
||||
contentWidth="medium"
|
||||
sizing="medium"
|
||||
background="none"
|
||||
cardStyle="solid"
|
||||
primaryButtonStyle="gradient"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="bold"
|
||||
>
|
||||
<NavbarStyleApple
|
||||
navItems={[{ name: "Home", id: "/" }, { name: "Reviews", id: "/reviews" }]}
|
||||
brandName="Reviews"
|
||||
/>
|
||||
<div className="relative isolate overflow-hidden">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8 py-16 sm:py-24">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">Customer Reviews</h1>
|
||||
<p className="mt-4 text-lg leading-8 text-foreground/70">
|
||||
Read what our amazing customers have to say about our products.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 mb-8">
|
||||
<Input
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
placeholder="Search reviews..."
|
||||
ariaLabel="Search reviews"
|
||||
className="w-full sm:w-1/2"
|
||||
/>
|
||||
<div className="flex gap-2 w-full sm:w-auto">
|
||||
<label htmlFor="sort-select" className="sr-only">Sort by</label>
|
||||
<select
|
||||
id="sort-select"
|
||||
value={sortCriteria}
|
||||
onChange={(e) => setSortCriteria(e.target.value)}
|
||||
className="block w-full rounded-md border-0 bg-card/70 py-2 pl-3 pr-10 text-foreground ring-1 ring-inset ring-border focus:ring-2 focus:ring-primary-cta sm:text-sm sm:leading-6"
|
||||
>
|
||||
<option value="newest">Newest First</option>
|
||||
<option value="oldest">Oldest First</option>
|
||||
<option value="highestRating">Highest Rating</option>
|
||||
<option value="lowestRating">Lowest Rating</option>
|
||||
<option value="mostHelpful">Most Helpful</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TestimonialCardThirteen
|
||||
id="customer-reviews-section"
|
||||
testimonials={testimonialCardData}
|
||||
title="What our customers are saying"
|
||||
description="Explore authentic feedback from real users who love our products. Your satisfaction is our priority, and we're proud of the experiences shared here."
|
||||
showRating={true}
|
||||
carouselMode="buttons"
|
||||
uniformGridCustomHeightClasses="min-h-none"
|
||||
animationType="slide-up"
|
||||
tag="Customer Stories"
|
||||
textboxLayout="default"
|
||||
cardClassName="flex flex-col p-6 border border-border rounded-lg shadow-md bg-card transition-all duration-300 hover:shadow-lg"
|
||||
contentWrapperClassName="flex flex-col h-full"
|
||||
testimonialClassName="flex-grow text-foreground/80 mt-2 text-sm leading-6"
|
||||
nameClassName="font-semibold text-lg"
|
||||
handleClassName="text-sm text-foreground/60"
|
||||
ratingClassName="flex items-center gap-1 text-primary-cta"
|
||||
imageWrapperClassName="w-12 h-12 rounded-full overflow-hidden flex-shrink-0"
|
||||
imageClassName="w-full h-full object-cover"
|
||||
gridClassName="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"
|
||||
/>
|
||||
|
||||
{hasMoreReviews && (
|
||||
<div className="text-center mt-12">
|
||||
<ButtonTextUnderline
|
||||
text="Load More Reviews"
|
||||
onClick={handleLoadMore}
|
||||
className="mt-6"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user