diff --git a/src/App.test.tsx b/src/App.test.tsx index e5b00cf..6762ac8 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -10,9 +10,9 @@ describe('App routing', () => { expect(screen.getByText(/hello@braidsbymira.com/)).toBeInTheDocument() }) - it('renders the shop placeholder at /shop', () => { + it('renders the Shop page at /shop', () => { render() - expect(screen.getByTestId('page-shop')).toBeInTheDocument() + expect(screen.getByRole('heading', { name: /shop/i })).toBeInTheDocument() }) it('renders the book placeholder at /book', () => { diff --git a/src/App.tsx b/src/App.tsx index 89de813..2db3b25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter, MemoryRouter, Routes, Route } from 'react-router-dom' import Layout from './components/Layout' +import Shop from './pages/Shop' interface AppProps { initialEntries?: string[] @@ -10,7 +11,7 @@ function AppRoutes() { }> } /> - } /> + } /> } /> } /> diff --git a/src/pages/Shop.module.css b/src/pages/Shop.module.css new file mode 100644 index 0000000..faddd50 --- /dev/null +++ b/src/pages/Shop.module.css @@ -0,0 +1,28 @@ +.page { + padding: var(--space-xl) var(--space-lg); +} + +.filters { + display: flex; + gap: var(--space-sm); + margin-bottom: var(--space-lg); +} + +.filters button { + background: none; + border: 1px solid var(--color-gold); + padding: 0.5rem 1rem; + cursor: pointer; + text-transform: capitalize; +} + +.filters button.active { + background: var(--color-charcoal); + color: var(--color-cream); +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: var(--space-lg); +} diff --git a/src/pages/Shop.test.tsx b/src/pages/Shop.test.tsx new file mode 100644 index 0000000..d3c6e8c --- /dev/null +++ b/src/pages/Shop.test.tsx @@ -0,0 +1,28 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { describe, it, expect } from 'vitest' +import Shop from './Shop' +import { products } from '../data/products' + +describe('Shop page', () => { + it('renders all products by default', () => { + render() + for (const p of products) { + expect(screen.getByText(p.name)).toBeInTheDocument() + } + }) + + it('filters products by category', async () => { + render() + await userEvent.click(screen.getByRole('button', { name: /^tools$/i })) + expect(screen.getByText('Steel Rat-Tail Comb')).toBeInTheDocument() + expect(screen.queryByText('Silk Edge Control')).not.toBeInTheDocument() + }) + + it('opens the inquiry modal when a product is inquired about', async () => { + render() + const buttons = screen.getAllByRole('button', { name: /ask about this/i }) + await userEvent.click(buttons[0]) + expect(screen.getByRole('button', { name: /send inquiry/i })).toBeInTheDocument() + }) +}) diff --git a/src/pages/Shop.tsx b/src/pages/Shop.tsx new file mode 100644 index 0000000..8c7ea30 --- /dev/null +++ b/src/pages/Shop.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react' +import ProductCard from '../components/ProductCard' +import InquiryModal from '../components/InquiryModal' +import { getProductsByCategory, type Product } from '../data/products' +import styles from './Shop.module.css' + +const CATEGORIES: (Product['category'] | 'all')[] = ['all', 'hair-care', 'extensions', 'tools', 'accessories'] + +function Shop() { + const [category, setCategory] = useState('all') + const [inquiring, setInquiring] = useState(null) + + return ( +
+

Shop

+
+ {CATEGORIES.map((c) => ( + + ))} +
+
+ {getProductsByCategory(category).map((product) => ( + + ))} +
+ {inquiring && setInquiring(null)} />} +
+ ) +} + +export default Shop