From f7cc03596a8a2ed27c6dc9dab5ef3411e7bcaa53 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 28 Apr 2026 11:42:34 -0700 Subject: [PATCH] Add green v2 design system --- package-lock.json | 10 +++ package.json | 1 + src/components/BrandMark.jsx | 19 ++++++ src/components/CopyButton.jsx | 24 +++---- src/components/InputForm.jsx | 63 +++++++---------- src/components/ResponseCard.jsx | 40 +++++------ src/components/Toast.jsx | 8 +-- src/components/ui/Button.jsx | 40 +++++++++++ src/components/ui/Surface.jsx | 9 +++ src/index.css | 12 ++-- src/pages/BuyerPage.jsx | 87 +++++++++++------------- src/pages/LandingPage.jsx | 117 +++++++++++++++++++++++--------- src/pages/SellerPage.jsx | 93 ++++++++++++------------- tailwind.config.js | 21 +++--- 14 files changed, 323 insertions(+), 221 deletions(-) create mode 100644 src/components/BrandMark.jsx create mode 100644 src/components/ui/Button.jsx create mode 100644 src/components/ui/Surface.jsx diff --git a/package-lock.json b/package-lock.json index 50cb6cf..5b04349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "haggly", "version": "0.1.0", "dependencies": { + "lucide-react": "^1.12.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -2465,6 +2466,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.12.0.tgz", + "integrity": "sha512-rTKR3RN6HIAxdNZALoPvqxd64vjL9nTThU0JF9q1Qg8yUnmo1r+d8baN72YNVK3RGxUmzBzbd77IWJq/fkm+Xw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index fc2feb6..87b8eae 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "preview": "vite preview" }, "dependencies": { + "lucide-react": "^1.12.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/src/components/BrandMark.jsx b/src/components/BrandMark.jsx new file mode 100644 index 0000000..63982ab --- /dev/null +++ b/src/components/BrandMark.jsx @@ -0,0 +1,19 @@ +import { MessageCircleMore } from 'lucide-react' + +function BrandMark({ compact = false }) { + return ( +
+ + + + Haggly + {!compact && ( + AI negotiation workspace + )} + +
+ ) +} + +export default BrandMark diff --git a/src/components/CopyButton.jsx b/src/components/CopyButton.jsx index 56a65b1..ee6b930 100644 --- a/src/components/CopyButton.jsx +++ b/src/components/CopyButton.jsx @@ -1,4 +1,6 @@ import { useState } from 'react' +import { Check, Copy } from 'lucide-react' +import Button from './ui/Button' function CopyButton({ text, onCopy }) { const [copied, setCopied] = useState(false) @@ -35,33 +37,23 @@ function CopyButton({ text, onCopy }) { } return ( - + ) } diff --git a/src/components/InputForm.jsx b/src/components/InputForm.jsx index c1d876d..b393f7b 100644 --- a/src/components/InputForm.jsx +++ b/src/components/InputForm.jsx @@ -1,4 +1,6 @@ import { useState } from 'react' +import { LoaderCircle, Sparkles } from 'lucide-react' +import Button from './ui/Button' function InputForm({ onGenerate, isGenerating }) { const [formData, setFormData] = useState({ @@ -59,23 +61,22 @@ function InputForm({ onGenerate, isGenerating }) { } const inputClasses = (fieldName) => ` - w-full px-4 py-3 rounded-xl border-2 transition-all duration-200 + w-full rounded-lg border px-4 py-3 transition-all duration-200 ${errors[fieldName] ? 'border-red-300 bg-red-50 focus:border-red-500 focus:ring-red-500/20' - : 'border-gray-200 bg-gray-50 focus:border-primary-500 focus:bg-white focus:ring-primary-500/20' + : 'border-stone-200 bg-stone-50 focus:border-primary-500 focus:bg-white focus:ring-primary-500/20' } focus:ring-4 focus:outline-none - placeholder:text-gray-400 + placeholder:text-stone-400 ` - const labelClasses = "block text-sm font-medium text-gray-700 mb-1.5" + const labelClasses = "block text-sm font-semibold text-stone-700 mb-1.5" return (
- {/* Item Name */}
{errors.item && ( -

{errors.item}

+

{errors.item}

)}
- {/* Price Inputs Grid */} -
- {/* Asking Price */} +
- + $
{errors.askingPrice && ( -

{errors.askingPrice}

+

{errors.askingPrice}

)}
- {/* Their Offer */}
- + $
{errors.theirOffer && ( -

{errors.theirOffer}

+

{errors.theirOffer}

)}
- {/* Minimum Price (Optional) */}
- + $
{errors.minimumPrice && ( -

{errors.minimumPrice}

+

{errors.minimumPrice}

)} -

+

The lowest you'd go before walking away

- {/* Submit Button */} - + ) } diff --git a/src/components/ResponseCard.jsx b/src/components/ResponseCard.jsx index e2812bb..059c196 100644 --- a/src/components/ResponseCard.jsx +++ b/src/components/ResponseCard.jsx @@ -1,24 +1,27 @@ import CopyButton from './CopyButton' -function ResponseCard({ tone, emoji, message, description, onCopy, delay = 0 }) { +function ResponseCard({ tone, message, description, onCopy, delay = 0 }) { const toneColors = { friendly: { - bg: 'bg-emerald-50', - border: 'border-emerald-200', - badge: 'bg-emerald-100 text-emerald-700', - accent: 'text-emerald-600' + bg: 'bg-primary-50', + border: 'border-primary-100', + badge: 'bg-primary-100 text-primary-800', + accent: 'text-primary-700', + dot: 'bg-primary-500', }, firm: { bg: 'bg-amber-50', - border: 'border-amber-200', - badge: 'bg-amber-100 text-amber-700', - accent: 'text-amber-600' + border: 'border-amber-100', + badge: 'bg-amber-100 text-amber-800', + accent: 'text-amber-800', + dot: 'bg-amber-500', }, casual: { - bg: 'bg-sky-50', - border: 'border-sky-200', - badge: 'bg-sky-100 text-sky-700', - accent: 'text-sky-600' + bg: 'bg-stone-50', + border: 'border-stone-200', + badge: 'bg-stone-200 text-stone-800', + accent: 'text-stone-600', + dot: 'bg-stone-500', } } @@ -27,30 +30,27 @@ function ResponseCard({ tone, emoji, message, description, onCopy, delay = 0 }) return (
- {/* Header */}
- {emoji} - + + {tone}
- {/* Message */} -
-

+

+

{message}

- {/* Description */} {description && (

{description} diff --git a/src/components/Toast.jsx b/src/components/Toast.jsx index 438964d..7a790c8 100644 --- a/src/components/Toast.jsx +++ b/src/components/Toast.jsx @@ -1,3 +1,5 @@ +import { Check } from 'lucide-react' + function Toast({ message }) { if (!message) { return null @@ -5,10 +7,8 @@ function Toast({ message }) { return (

-
- - - +
+
diff --git a/src/components/ui/Button.jsx b/src/components/ui/Button.jsx new file mode 100644 index 0000000..4f4ec8e --- /dev/null +++ b/src/components/ui/Button.jsx @@ -0,0 +1,40 @@ +const variantClasses = { + primary: 'bg-primary-700 text-white hover:bg-primary-800 shadow-sm shadow-primary-900/15', + secondary: 'border border-primary-200 bg-white text-primary-900 hover:border-primary-300 hover:bg-primary-50', + ghost: 'text-primary-800 hover:bg-primary-50', + muted: 'bg-primary-50 text-primary-800 hover:bg-primary-100', +} + +const sizeClasses = { + sm: 'h-9 px-3 text-sm', + md: 'h-11 px-4 text-sm', + lg: 'min-h-14 px-5 py-3 text-base', +} + +function Button({ + children, + className = '', + variant = 'primary', + size = 'md', + type = 'button', + ...props +}) { + return ( + + ) +} + +export default Button diff --git a/src/components/ui/Surface.jsx b/src/components/ui/Surface.jsx new file mode 100644 index 0000000..f332c09 --- /dev/null +++ b/src/components/ui/Surface.jsx @@ -0,0 +1,9 @@ +function Surface({ children, className = '' }) { + return ( +
+ {children} +
+ ) +} + +export default Surface diff --git a/src/index.css b/src/index.css index ba5cea1..0f58651 100644 --- a/src/index.css +++ b/src/index.css @@ -8,17 +8,17 @@ } ::-webkit-scrollbar-track { - background: #f1f1f1; + background: #f4f1ea; border-radius: 3px; } ::-webkit-scrollbar-thumb { - background: #c084fc; + background: #83d9a8; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: #a855f7; + background: #17824d; } /* Smooth transitions */ @@ -29,8 +29,6 @@ /* Better focus states */ input:focus, button:focus { outline: none; - ring: 2px; - ring-color: #a855f7; } /* Toast animation */ @@ -55,7 +53,7 @@ input:focus, button:focus { /* Gradient text helper */ .gradient-text { - background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%); + background: linear-gradient(135deg, #17824d 0%, #0d442c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -68,7 +66,7 @@ input:focus, button:focus { .card-hover:hover { transform: translateY(-2px); - box-shadow: 0 10px 25px -5px rgba(147, 51, 234, 0.2); + box-shadow: 0 10px 25px -5px rgba(18, 103, 63, 0.18); } /* Input styling overrides */ diff --git a/src/pages/BuyerPage.jsx b/src/pages/BuyerPage.jsx index ba3a7d2..6d1af91 100644 --- a/src/pages/BuyerPage.jsx +++ b/src/pages/BuyerPage.jsx @@ -1,56 +1,47 @@ +import { ArrowLeft, BadgeQuestionMark, Car, MessageSquareText, Search } from 'lucide-react' +import BrandMark from '../components/BrandMark' +import Button from '../components/ui/Button' + function BuyerPage({ onBack }) { + const upcomingTools = [ + { icon: Search, label: 'Question builder' }, + { icon: MessageSquareText, label: 'Response scripts' }, + { icon: BadgeQuestionMark, label: 'Deal check' }, + { icon: Car, label: 'Car buying mode' }, + ] + return ( -
-
-
-
- -
- 🛒 -
-

Haggly

-
-

- Buyer Tools - Coming Soon! +

+
+ + +
+ +
+
+

+ Buyer workspace +

+

+ Buyer tools are next in line. +

+

+ This screen is ready for the buyer chat flow once the mock chat layer is added.

-
-
-
-
-
🚧
-

Coming Very Soon!

-

- We're building amazing buyer tools including: -

-
    -
  • - - Strategic questioning techniques -
  • -
  • - - Negotiation scripts for different scenarios -
  • -
  • - - "Guard down" moment detection -
  • -
  • - - Car buying specific strategies -
  • -
-

- For now, try our seller tools or check back soon! -

-
+
+ {upcomingTools.map(({ icon: Icon, label }) => ( +
+ + + {label} +
+ ))}
diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx index d5f1932..4bd01f9 100644 --- a/src/pages/LandingPage.jsx +++ b/src/pages/LandingPage.jsx @@ -1,36 +1,91 @@ +import { ArrowRight, ShoppingBag, Tag } from 'lucide-react' +import BrandMark from '../components/BrandMark' +import Button from '../components/ui/Button' +import Surface from '../components/ui/Surface' + function LandingPage({ onSelectUserType }) { return ( -
-
-
-

💬 Haggly

-

Your AI-powered negotiation assistant

-
- -

Are you buying or selling?

- -
- - - -
- -

- Get personalized scripts and strategies for any negotiation -

-
+
+
+
+ + + v2 preview + +
+ +
+
+

+ No-login negotiation workspace +

+

+ Start the next message with a clearer plan. +

+

+ Pick a side, add the deal context, and draft a response you can send with confidence. +

+ +
+ + + +
+
+ + +
+
+

Workspace preview

+

Local, fast, and ready for the AI chat layer.

+
+ + no login + +
+ +
+
+

Current focus

+

Marketplace counter offer

+

+ Generate a firm but fair response, keep the tone calm, and move toward pickup details. +

+
+ +
+ {['Context', 'Drafts', 'Outcome'].map((label) => ( +
+

{label}

+

+

+ ))} +
+
+
+
+
) } diff --git a/src/pages/SellerPage.jsx b/src/pages/SellerPage.jsx index 6748b17..fc51b98 100644 --- a/src/pages/SellerPage.jsx +++ b/src/pages/SellerPage.jsx @@ -1,58 +1,60 @@ +import { ArrowLeft, Lightbulb, RotateCcw } from 'lucide-react' +import BrandMark from '../components/BrandMark' import InputForm from '../components/InputForm' import ResponseCard from '../components/ResponseCard' import Toast from '../components/Toast' +import Button from '../components/ui/Button' +import Surface from '../components/ui/Surface' function SellerPage({ responses, toast, isGenerating, onBack, onGenerate, onReset, onCopy }) { return ( -
+
-
-
- - -
-
- 📤 -
-

Haggly

-
+
+ + +
-

- Generate perfect responses for marketplace negotiations +

+
+

+ Seller workspace

-

- Never freeze up when someone lowballs you again +

+ Turn an offer into a clear reply. +

+

+ Add the numbers, keep the boundary, and generate three response styles.

-
-
-
-
-
+
+

Current mode

+

Template responses now. Chat and local dashboard are the next slices.

+
+ + +
+ -
+ {responses && (
-
+

Your Responses

- +

@@ -63,7 +65,6 @@ function SellerPage({ responses, toast, isGenerating, onBack, onGenerate, onRese onCopy('Copied to clipboard!')} @@ -71,13 +72,14 @@ function SellerPage({ responses, toast, isGenerating, onBack, onGenerate, onRese /> ))} -

-

- 💡 Quick Tips +
+

+

-
    -
  • • Be polite but firm — you set the price for a reason
  • -
  • • It's okay to say no — good buyers respect fair prices
  • +
      +
    • • Be polite but firm because you set the price for a reason
    • +
    • • It's okay to say no because good buyers respect fair prices
    • • Don't take lowballs personally, just part of the game
@@ -85,16 +87,15 @@ function SellerPage({ responses, toast, isGenerating, onBack, onGenerate, onRese )} {!responses && ( -
-
💬
-

Fill in the details above to generate responses

+
+

Responses will appear here.

)} -
+

-
) diff --git a/tailwind.config.js b/tailwind.config.js index 54328b8..f1e6c96 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,18 +7,17 @@ export default { theme: { extend: { colors: { - // Fun purple palette primary: { - 50: '#faf5ff', - 100: '#f3e8ff', - 200: '#e9d5ff', - 300: '#d8b4fe', - 400: '#c084fc', - 500: '#a855f7', - 600: '#9333ea', - 700: '#7c3aed', - 800: '#6b21a8', - 900: '#581c87', + 50: '#effcf4', + 100: '#d9f7e4', + 200: '#b7edcc', + 300: '#83d9a8', + 400: '#4fbe80', + 500: '#259f5f', + 600: '#17824d', + 700: '#12673f', + 800: '#105234', + 900: '#0d442c', } }, fontFamily: {