mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 05:49:41 +02:00
- Remove recursive `turbo run type-check` from parent packages (chat, zitare, voxel-lava) - Increase turbo concurrency from 2 to 5 - Add documentation for turbo anti-pattern in CLAUDE.md - Skip type-check temporarily for apps with pending migrations - Update picture mobile stores to use camelCase API response properties - Add shared-nestjs-auth dependency to chat and picture backends - Clean up unused design-token files from picture package - Update shared-landing-ui components and feedback service config
121 lines
3.9 KiB
Text
121 lines
3.9 KiB
Text
---
|
|
/**
|
|
* Shared Testimonial Section component
|
|
* Displays customer testimonials in a grid
|
|
*/
|
|
import Container from '../atoms/Container.astro';
|
|
import Card from '../atoms/Card.astro';
|
|
import SectionHeader from '../atoms/SectionHeader.astro';
|
|
|
|
interface Testimonial {
|
|
name: string;
|
|
role?: string;
|
|
company?: string;
|
|
text: string;
|
|
image?: string;
|
|
rating?: number;
|
|
}
|
|
|
|
interface Props {
|
|
title: string;
|
|
subtitle?: string;
|
|
testimonials: Testimonial[];
|
|
columns?: 1 | 2 | 3;
|
|
showRating?: boolean;
|
|
class?: string;
|
|
id?: string;
|
|
}
|
|
|
|
const {
|
|
title,
|
|
subtitle,
|
|
testimonials,
|
|
columns = 3,
|
|
showRating = true,
|
|
class: className = '',
|
|
id
|
|
} = Astro.props;
|
|
|
|
const gridCols = {
|
|
1: 'max-w-2xl mx-auto',
|
|
2: 'md:grid-cols-2 max-w-4xl mx-auto',
|
|
3: 'md:grid-cols-2 lg:grid-cols-3'
|
|
};
|
|
---
|
|
|
|
<section id={id} class:list={["py-16 md:py-24", className]}>
|
|
<Container>
|
|
<SectionHeader title={title} subtitle={subtitle} />
|
|
|
|
<div class:list={["grid gap-6 md:gap-8", gridCols[columns]]}>
|
|
{testimonials.map((testimonial) => (
|
|
<Card variant="bordered" padding="lg">
|
|
<div class="flex flex-col h-full">
|
|
<!-- Header with avatar and info -->
|
|
<div class="flex items-start gap-4 mb-4">
|
|
<div class="flex-shrink-0">
|
|
{testimonial.image ? (
|
|
<img
|
|
src={testimonial.image}
|
|
alt={testimonial.name}
|
|
class="w-12 h-12 rounded-full object-cover border-2 border-[var(--color-primary)]/20"
|
|
width="48"
|
|
height="48"
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
) : (
|
|
<div class="w-12 h-12 rounded-full bg-[var(--color-primary)]/20 flex items-center justify-center">
|
|
<span class="text-xl font-semibold text-[var(--color-primary)]">
|
|
{testimonial.name.charAt(0)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<h3 class="font-semibold text-[var(--color-text-primary)] truncate">
|
|
{testimonial.name}
|
|
</h3>
|
|
{testimonial.role && (
|
|
<p class="text-sm text-[var(--color-text-secondary)] truncate">
|
|
{testimonial.role}
|
|
</p>
|
|
)}
|
|
{testimonial.company && (
|
|
<p class="text-sm text-[var(--color-text-muted)] truncate">
|
|
{testimonial.company}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quote -->
|
|
<blockquote class="flex-1 text-[var(--color-text-secondary)] italic mb-4 leading-relaxed">
|
|
"{testimonial.text}"
|
|
</blockquote>
|
|
|
|
<!-- Rating -->
|
|
{showRating && testimonial.rating && (
|
|
<div class="flex items-center gap-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<svg
|
|
class:list={[
|
|
"w-5 h-5",
|
|
i < (testimonial.rating ?? 0) ? "text-[var(--color-primary)]" : "text-[var(--color-border)]"
|
|
]}
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
|
</svg>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
<slot />
|
|
</Container>
|
|
</section>
|