From 8504665ea5f6ca6030fb720850a986670d334c24 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 28 Apr 2026 12:31:05 -0700 Subject: [PATCH 1/2] Add mock chat MVP --- .github/workflows/ci.yml | 6 ++ .gitignore | 2 + package-lock.json | 64 ++++++++++++++++++++ package.json | 2 + playwright.config.js | 24 ++++++++ src/App.jsx | 36 ++--------- src/features/chat/ChatComposer.jsx | 44 ++++++++++++++ src/features/chat/ChatMessage.jsx | 28 +++++++++ src/features/chat/ChatPanel.jsx | 59 ++++++++++++++++++ src/features/chat/mockAssistant.js | 97 ++++++++++++++++++++++++++++++ src/pages/BuyerPage.jsx | 35 ++++++----- src/pages/SellerPage.jsx | 78 +++++------------------- tests/e2e/chat.spec.js | 31 ++++++++++ 13 files changed, 397 insertions(+), 109 deletions(-) create mode 100644 playwright.config.js create mode 100644 src/features/chat/ChatComposer.jsx create mode 100644 src/features/chat/ChatMessage.jsx create mode 100644 src/features/chat/ChatPanel.jsx create mode 100644 src/features/chat/mockAssistant.js create mode 100644 tests/e2e/chat.spec.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c44673e..705d94d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,5 +29,11 @@ jobs: - name: Test run: npm test + - name: Install Playwright browser + run: npx playwright install chromium + + - name: E2E + run: npm run test:e2e + - name: Build run: npm run build diff --git a/.gitignore b/.gitignore index a26a3cb..454bd12 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ dist/ coverage/ AI_REVIEW_BRIEF.md .claude/ +test-results/ +playwright-report/ diff --git a/package-lock.json b/package-lock.json index 5b04349..5364eb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@playwright/test": "^1.59.1", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^6.0.1", @@ -626,6 +627,22 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.17", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", @@ -2742,6 +2759,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", diff --git a/package.json b/package.json index 87b8eae..1f941b8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "vite build", "lint": "eslint . --report-unused-disable-directives --max-warnings 0", "test": "vitest run", + "test:e2e": "playwright test", "preview": "vite preview" }, "dependencies": { @@ -17,6 +18,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@playwright/test": "^1.59.1", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^6.0.1", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..0c439cf --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,24 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './tests/e2e', + timeout: 30_000, + expect: { + timeout: 5_000, + }, + use: { + baseURL: 'http://127.0.0.1:3000', + trace: 'on-first-retry', + }, + webServer: { + command: 'npm run dev -- --host 127.0.0.1 --port 3000 --strictPort', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/src/App.jsx b/src/App.jsx index cbca8d8..b768183 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,57 +2,33 @@ import { useState } from 'react' import LandingPage from './pages/LandingPage' import BuyerPage from './pages/BuyerPage' import SellerPage from './pages/SellerPage' -import { generateMessages } from './utils/messageGenerator' function App() { const [userType, setUserType] = useState(null) // 'buyer' or 'seller' - const [responses, setResponses] = useState(null) const [toast, setToast] = useState(null) - const [isGenerating, setIsGenerating] = useState(false) - - const handleGenerate = (formData) => { - setIsGenerating(true) - - // Small delay for visual feedback - setTimeout(() => { - const messages = generateMessages(formData) - setResponses(messages) - setIsGenerating(false) - - // Scroll to results on mobile - setTimeout(() => { - document.getElementById('results')?.scrollIntoView({ behavior: 'smooth' }) - }, 100) - }, 300) - } const showToast = (message) => { setToast(message) setTimeout(() => setToast(null), 2000) } - const handleReset = () => { - setResponses(null) - setUserType(null) - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - if (!userType) { return } if (userType === 'buyer') { - return setUserType(null)} /> + return ( + setUserType(null)} + onCopy={showToast} + /> + ) } return ( setUserType(null)} - onGenerate={handleGenerate} - onReset={handleReset} onCopy={showToast} /> ) diff --git a/src/features/chat/ChatComposer.jsx b/src/features/chat/ChatComposer.jsx new file mode 100644 index 0000000..219ce57 --- /dev/null +++ b/src/features/chat/ChatComposer.jsx @@ -0,0 +1,44 @@ +import { Send } from 'lucide-react' +import { useState } from 'react' +import Button from '../../components/ui/Button' + +function ChatComposer({ disabled, onSend }) { + const [draft, setDraft] = useState('') + + const handleSubmit = (event) => { + event.preventDefault() + const message = draft.trim() + + if (!message) { + return + } + + onSend(message) + setDraft('') + } + + return ( +
+
+ +