diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6ad46e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,68 @@ +# Marketplace Project Notes + +## Database: PostgreSQL via Docker + +Container name: `marketplace-postgres` +Image: `postgres:17-alpine` +Volume: `marketplace-pgdata` (persistent local data) + +### Start database +```bash +docker start marketplace-postgres +``` + +### Stop database (frees memory) +```bash +docker stop marketplace-postgres +``` + +### Check status +```bash +docker ps -f name=marketplace-postgres +``` + +### Connection string +``` +postgresql://marketplace:marketplace_dev@localhost:5432/marketplace +``` + +### If container was deleted, recreate: +```bash +docker run -d \ + --name marketplace-postgres \ + -e POSTGRES_USER=marketplace \ + -e POSTGRES_PASSWORD=marketplace_dev \ + -e POSTGRES_DB=marketplace \ + -p 5432:5432 \ + -v marketplace-pgdata:/var/lib/postgresql/data \ + --restart unless-stopped \ + postgres:17-alpine +``` + +Data persists in the `marketplace-pgdata` Docker volume even if the container is removed. + +## Running the app + +1. Start database: `docker start marketplace-postgres` +2. Server: `npm run dev:server` (port 3000) +3. Client: `npm run dev:client` (port 5173) +4. Or both: `npm run dev` + +## Seed data + +```bash +cd server && npx tsx prisma/seed.ts +``` + +Test users (all password: `password123`): +- alice.chen@example.com +- bob.smith@example.com +- carol.jones@example.com +- david.wilson@example.com +- eva.martinez@example.com + +## Key env vars (server/.env) + +- `DATABASE_URL` — PostgreSQL connection +- `GOOGLE_MAPS_API_KEY` — Location autocomplete +- `STRIPE_SECRET_KEY` / `STRIPE_PUBLISHABLE_KEY` — Payments (optional for dev) diff --git a/client/src/components/layout/DashboardLayout.tsx b/client/src/components/layout/DashboardLayout.tsx index 2904135..fd205fe 100644 --- a/client/src/components/layout/DashboardLayout.tsx +++ b/client/src/components/layout/DashboardLayout.tsx @@ -1,28 +1,46 @@ -import { NavLink, Outlet } from 'react-router-dom'; -import { MessageSquare, Tag, Bell, ShoppingBag, Settings } from 'lucide-react'; +import { NavLink, Outlet, useNavigate } from 'react-router-dom'; +import { MessageSquare, Tag, ShoppingBag, Settings, List, Heart, LogOut } from 'lucide-react'; +import { useAuth } from '../../context/AuthContext'; const navItems = [ - { to: '/dashboard/messages', icon: MessageSquare, label: 'Messages' }, { to: '/dashboard/offers', icon: Tag, label: 'Offers' }, - { to: '/dashboard/notifications', icon: Bell, label: 'Notifications' }, { to: '/dashboard/sold', icon: ShoppingBag, label: 'Sold Items' }, + { to: '/dashboard/listings', icon: List, label: 'My Listings' }, + { to: '/dashboard/saved', icon: Heart, label: 'Saved Items' }, + { to: '/dashboard/messages', icon: MessageSquare, label: 'My Messages' }, { to: '/dashboard/settings', icon: Settings, label: 'Settings' }, ]; export function DashboardLayout() { + const { logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = () => { + logout(); + navigate('/'); + }; + return (
{/* Sidebar */} diff --git a/client/src/components/layout/Footer.tsx b/client/src/components/layout/Footer.tsx index f079677..e350be6 100644 --- a/client/src/components/layout/Footer.tsx +++ b/client/src/components/layout/Footer.tsx @@ -1,7 +1,25 @@ +import { useState } from 'react'; import { Link } from 'react-router-dom'; import { Facebook, Twitter, Instagram, Youtube, Package } from 'lucide-react'; +import { api } from '../../api/client'; export function Footer() { + const [email, setEmail] = useState(''); + const [subscribing, setSubscribing] = useState(false); + const [subscribed, setSubscribed] = useState(false); + + const handleSubscribe = async (e: React.FormEvent) => { + e.preventDefault(); + if (!email || subscribing) return; + setSubscribing(true); + try { + await api.post('/newsletter', { email }); + setSubscribed(true); + setEmail(''); + } catch {} + setSubscribing(false); + }; + return (
@@ -20,12 +38,18 @@ export function Footer() {

Stay Updated

Subscribe to our newsletter for tips and special offers

-
- - -
+ {subscribed ? ( +

Thanks for subscribing!

+ ) : ( +
+ setEmail(e.target.value)} required + className="flex-1 px-3 py-2 rounded-lg bg-primary-800 border border-primary-700 text-sm placeholder:text-primary-400 focus:outline-none focus:border-primary-500" /> + +
+ )}
@@ -36,7 +60,7 @@ export function Footer() {
  • Home
  • Browse
  • Sell Your Item
  • -
  • My Listings
  • +
  • My Listings
  • diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index d0bbb2f..10312e1 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import { Search, Bell, Menu, X, User, LogOut, Package, Heart, MessageSquare, Settings } from 'lucide-react'; +import { Search, Bell, Menu, X, User, LogOut, ShoppingBag, Heart, MessageSquare, Settings } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; import { Avatar } from '../ui/Avatar'; @@ -19,15 +19,15 @@ export function Header() { }; return ( -
    +
    {/* Logo */} -
    - +
    +
    - + MARKETPLACE @@ -35,14 +35,14 @@ export function Header() { {/* Search */}
    - + setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 rounded-xl border border-gray-200 bg-gray-50 text-sm - focus:border-primary-400 focus:ring-2 focus:ring-primary-100 focus:outline-none transition-all" + className="w-full pl-10 pr-4 py-2 rounded-xl border border-white/20 bg-white/10 text-sm text-white placeholder-white/50 + focus:border-white/40 focus:ring-2 focus:ring-white/20 focus:outline-none transition-all backdrop-blur" />
    @@ -51,15 +51,15 @@ export function Header() {
    {isAuthenticated ? ( <> - + Sell Item - - + + - - - + + +
    {/* Mobile menu */} {showMobileMenu && ( -
    +
    - + setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 rounded-xl border border-gray-200 bg-gray-50 text-sm focus:border-primary-400 focus:outline-none" /> + className="w-full pl-10 pr-4 py-2 rounded-xl border border-white/20 bg-white/10 text-sm text-white placeholder-white/50 focus:border-white/40 focus:outline-none" />
    {isAuthenticated && ( diff --git a/client/src/components/listings/CategorySidebar.tsx b/client/src/components/listings/CategorySidebar.tsx index 8bd7a30..a84df7a 100644 --- a/client/src/components/listings/CategorySidebar.tsx +++ b/client/src/components/listings/CategorySidebar.tsx @@ -19,12 +19,11 @@ interface CategorySidebarProps { export function CategorySidebar({ selected, onSelect }: CategorySidebarProps) { return ( -