Transcript
Agent claude-code
Model claude-opus-5-5
Events 27
Status Success
Turns 2Tool Calls 12Thinking 0Errors 0
Unknown 9Shell 3
Shell (3)
cat src/components/ReviewCard.tsx; ls src/components stories; grep -rn "ReviewCard" src stories --include=*.tsx --include=*.ts | grep -v "^src/components/ReviewCard.tsx"cat stories/ReviewCard.stories.tsx; grep -n reshaped package.jsoncat > src/components/ReviewCard.tsx <<'EOF'
import { View, Text, Button } from 'reshaped';
export type ReviewCardProps = {
author: string;
rating: number;
comment: string;
/** Review date as a `Date` or ISO 8601 string */
date: Date | string;
/** When provided, renders a Report button that calls this handler */
onReport?: () => void;
};
const dateFormatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
});
export default function ReviewCard({ author, rating, comment, date, onReport }: ReviewCardProps) {
const clamped = Math.min(5, Math.max(0, rating));
const parsedDate = date instanceof Date ? date : new Date(date);
const isValidDate = !Number.isNaN(parsedDate.getTime());
return (
<View
borderColor="neutral-faded"
border={true}
borderRadius="medium"
padding={3}
backgroundColor="elevation-base"
attributes={{ 'data-testid': 'review-card' }}
>
<View gap={2} direction="column">
<View direction="row" justify="space-between" align="center">
<View direction="column">
<Text variant="body-2" weight="bold" attributes={{ 'data-testid': 'review-author' }}>
{author}
</Text>
<Text
variant="caption-1"
color="neutral-faded"
as="time"
attributes={{
'data-testid': 'review-date',
dateTime: isValidDate ? parsedDate.toISOString() : undefined,
}}
>
{isValidDate ? dateFormatter.format(parsedDate) : String(date)}
</Text>
</View>
<Text
variant="body-2"
attributes={{
'data-testid': 'review-rating',
'aria-label': `${clamped} out of 5`,
}}
>
{'★'.repeat(clamped)}
{'☆'.repeat(5 - clamped)}
</Text>
</View>
<Text
variant="body-2"
color="neutral-faded"
as="p"
attributes={{
'data-testid': 'review-comment',
style: { margin: 0 },
}}
>
{comment}
</Text>
{onReport && (
<View direction="row" justify="end">
<Button
size="small"
variant="ghost"
color="critical"
onClick={() => onReport()}
attributes={{
'data-testid': 'review-report',
'aria-label': `Report review by ${author}`,
}}
>
Report
</Button>
</View>
)}
</View>
</View>
);
}
EOF
cat > stories/ReviewCard.stories.tsx <<'EOF'
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, fn } from 'storybook/test';
import ReviewCard from '../src/components/ReviewCard';
const meta = {
title: 'Reviews/ReviewCard',
component: ReviewCard,
args: {
author: 'Avery Doe',
rating: 4,
comment: 'Great product, quick support response, would recommend.',
date: '2026-09-28T14:30:00Z',
},
} satisfies Meta<typeof ReviewCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
play: async ({ canvas }) => {
const date = canvas.getByTestId('review-date');
await expect(date).toHaveTextContent('Sep 28, 2026');
await expect(date).toHaveAttribute('datetime', '2026-09-28T14:30:00.000Z');
await expect(canvas.queryByRole('button', { name: /report/i })).not.toBeInTheDocument();
},
};
export const WithLongComment: Story = {
args: {
comment:
'I was skeptical at first, but this worked really well for our team. The onboarding took a day, and we were productive right away. Highly recommended for small teams.',
},
};
export const LowRating: Story = {
args: {
rating: 2,
comment: 'Functionality is okay, but performance can be improved.',
},
};
export const Reportable: Story = {
args: {
onReport: fn(),
},
play: async ({ canvas, args, userEvent }) => {
const button = canvas.getByRole('button', { name: 'Report review by Avery Doe' });
await expect(button).toHaveTextContent('Report');
await userEvent.click(button);
await expect(args.onReport).toHaveBeenCalledTimes(1);
},
};
EOF
npx tsc -p tsconfig.app.json --noEmit 2>&1 | tail; npx eslint src/components/ReviewCard.tsx stories/ReviewCard.stories.tsx