diff --git a/frontend/README.md b/frontend/README.md index 7c08e40..f9b8ede 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -37,12 +37,45 @@ A modern, minimal web app for ShopLyft, built with **React 19**, **TypeScript**, ## Project Structure +``` +frontend/src/ +├── main.tsx # React app entry point with root rendering +├── App.tsx # Main app component with view state management +├── index.css # Global styles and Tailwind CSS imports +├── assets/ # Static assets (images, icons, etc.) +└── components/ # Component library organized by feature + ├── types/ + │ └── index.ts # Shared TypeScript interfaces and types + ├── shared/ # Reusable components across features + │ ├── ui/ + │ │ └── StepContainer.tsx # Motion wrapper for form steps + │ ├── forms/ + │ │ ├── StepHeader.tsx # Title and description component + │ │ └── NavigationButtons.tsx # Back/Continue navigation buttons + │ └── layout/ + │ └── CardContainer.tsx # Responsive card wrapper component + └── features/ # Feature-specific components + ├── landing/ # Landing page feature + │ └── LandingPage/ + │ ├── LandingPage.tsx # Main landing page container + │ ├── AnimatedHeadline.tsx # Cycling headline animation + │ ├── DesktopLandingView.tsx # Desktop layout with text-first + │ ├── MobileLandingView.tsx # Mobile layout with icon-first + │ ├── LandingContent.tsx # Description text component + │ └── GetStartedButton.tsx # CTA button with variants + └── shopping/ # Shopping list feature + ├── ShoppingListForm.tsx # Main multi-step form container + ├── ItemSelectionGrid.tsx # Grid for common item selection + ├── CustomInputToggle.tsx # Toggle with textarea for custom input + ├── ItemDetailsForm.tsx # Quantity and notes input form + ├── LocationSelector.tsx # Location selection with geolocation + ├── ShoppingListTable.tsx # Editable shopping list table + ├── LoadingAnimation.tsx # Loading state with animated cart + ├── PlanLayout.tsx # Final plan display component + └── ShoppingCartIcon.tsx # Animated shopping cart icon +``` + - `public/` — Static files (e.g., favicon) -- `src/` — Source code - - `main.tsx` — Entry point - - `App.tsx` — Main app logic, animated transitions - - `LandingPage.tsx` — Animated hero section - - `index.css` — Tailwind import - `tailwind.config.js` — TailwindCSS config (custom color schema) - `vite.config.ts` — Vite config with Tailwind plugin @@ -74,25 +107,39 @@ A modern, minimal web app for ShopLyft, built with **React 19**, **TypeScript**, --- -## Component Breakdown - -- `App` — Main app logic, animated transitions between views -- `LandingPage` — Animated hero section, cycling headlines, Get Started button -- `CardContainer` — Shopping list input form (currently inline in App) -- *(Planned/Upcoming)* - - `Header` - - `ShoppingForm` - - `LocationAutocomplete` - - `PreferencesPanel` - - `StoreFilter` - - `ResultsPage` - - `StoreCard` - - `BasketList` - - `SavingsHeadline` - - `SummarySection` - - `AssumptionsWarnings` - - `ClickCollectButton` - - `DownloadPlanButton` +## Component Architecture + +### **Shared Components** (Reusable across features) + +- `StepContainer` — Motion wrapper with consistent animations for form steps +- `StepHeader` — Standardized title and description layout +- `NavigationButtons` — Back/Continue buttons with consistent styling +- `CardContainer` — Responsive card wrapper with mobile/desktop variants + +### **Landing Page Feature** + +- `LandingPage` — Main container with responsive view switching +- `AnimatedHeadline` — Cycling headline animation with typewriter effect +- `DesktopLandingView` — Desktop layout (text-first, icon-second) +- `MobileLandingView` — Mobile layout (icon-first, text-second) +- `LandingContent` — Description text with alignment variants +- `GetStartedButton` — CTA button with size variants + +### **Shopping List Feature** + +- `ShoppingListForm` — Multi-step form container with state management +- `ItemSelectionGrid` — Grid layout for common grocery item selection +- `CustomInputToggle` — Toggle switch with expandable textarea +- `ItemDetailsForm` — Form for editing quantities and notes +- `LocationSelector` — Location input with geolocation support +- `ShoppingListTable` — Editable table with add/edit/remove functionality +- `LoadingAnimation` — Loading state with animated shopping cart +- `PlanLayout` — Final plan display with store cards and savings +- `ShoppingCartIcon` — Animated cart icon with size variants + +### **Type Definitions** + +- `types/index.ts` — Centralized TypeScript interfaces for all components --- diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c42ba39..cc8341b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,27 +1,73 @@ import { useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import LandingPage from "./components/LandingPage/LandingPage"; -import ShoppingListForm from "./components/ShoppingListForm/ShoppingListForm"; +import LandingPage from "./components/features/landing/LandingPage/LandingPage"; +import ShoppingListForm from "./components/features/shopping/ShoppingListForm"; +import TemplateTest from "./components/features/shopping/TemplateTest"; function App() { - const [showLanding, setShowLanding] = useState(true); + const [currentView, setCurrentView] = useState< + "landing" | "shopping" | "test" + >("landing"); + + const renderView = () => { + switch (currentView) { + case "landing": + return setCurrentView("shopping")} />; + case "shopping": + return ; + case "test": + return ; + default: + return setCurrentView("shopping")} />; + } + }; return (
+ {/* Test Navigation */} +
+ + + +
+ - {showLanding ? ( - - setShowLanding(false)} /> - - ) : ( - - )} + + {renderView()} +
); diff --git a/frontend/src/assets/aldi.png b/frontend/src/assets/aldi.png new file mode 100644 index 0000000..d14ef64 Binary files /dev/null and b/frontend/src/assets/aldi.png differ diff --git a/frontend/src/assets/coles.png b/frontend/src/assets/coles.png new file mode 100644 index 0000000..3db2c2f Binary files /dev/null and b/frontend/src/assets/coles.png differ diff --git a/frontend/src/assets/woolworth.png b/frontend/src/assets/woolworth.png new file mode 100644 index 0000000..c774d74 Binary files /dev/null and b/frontend/src/assets/woolworth.png differ diff --git a/frontend/src/components/ShoppingListForm/ShoppingListForm.tsx b/frontend/src/components/ShoppingListForm/ShoppingListForm.tsx deleted file mode 100644 index 1412913..0000000 --- a/frontend/src/components/ShoppingListForm/ShoppingListForm.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import CardContainer from "../CardContainer"; - -// Sample grocery items - in a real app, this would come from an API -const availableItems = [ - "Milk", - "Eggs", - "Bread", - "Butter", - "Cheese", - "Yogurt", - "Chicken", - "Beef", - "Fish", - "Rice", - "Pasta", - "Tomatoes", - "Onions", - "Carrots", - "Potatoes", - "Apples", - "Bananas", - "Oranges", - "Spinach", - "Lettuce", - "Cucumber", - "Olive Oil", - "Salt", - "Pepper", - "Garlic", - "Ginger", - "Lemon", - "Lime", - "Cereal", - "Oats", - "Nuts", - "Coffee", - "Tea", - "Sugar", - "Flour", - "Honey", -]; - -function ShoppingListForm() { - const [searchTerm, setSearchTerm] = useState(""); - const [selectedItems, setSelectedItems] = useState([]); - - const filteredItems = availableItems.filter((item) => - item.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - const toggleItem = (item: string) => { - setSelectedItems((prev) => - prev.includes(item) ? prev.filter((i) => i !== item) : [...prev, item] - ); - }; - - const handleSubmit = () => { - if (selectedItems.length === 0) { - alert("Please select at least one item for your shopping list."); - return; - } - console.log("Selected items:", selectedItems); - // Here you would typically send the data to your backend - alert(`Shopping list created with ${selectedItems.length} items!`); - }; - - return ( -
- - -
-
-

- Create Your Shopping List -

-

- Search and select items you need to buy -

-
- - {/* Search Input */} -
- setSearchTerm(e.target.value)} - placeholder="Search for items..." - className="w-full border border-orange-300 rounded-lg p-4 pr-12 text-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent" - /> -
- 🔍 -
-
- - {/* Selected Items Count */} - {selectedItems.length > 0 && ( - - - {selectedItems.length} item - {selectedItems.length !== 1 ? "s" : ""} selected - - - )} - - {/* Items Grid */} -
- - {filteredItems.map((item) => { - const isSelected = selectedItems.includes(item); - return ( - toggleItem(item)} - className={` - p-3 rounded-lg border-2 transition-all duration-200 text-sm font-medium - ${ - isSelected - ? "bg-orange-500 border-orange-500 text-white shadow-lg" - : "bg-white border-orange-200 text-orange-700 hover:border-orange-300 hover:bg-orange-50" - } - `} - > -
- {isSelected && } - {item} -
-
- ); - })} -
-
- - {/* No Results */} - {filteredItems.length === 0 && searchTerm && ( - -

No items found for "{searchTerm}"

-

Try a different search term

-
- )} - - {/* Submit Button */} -
- - Create Shopping List ({selectedItems.length}) - -
-
-
-
-
- ); -} - -export default ShoppingListForm; diff --git a/frontend/src/components/LandingPage/AnimatedHeadline.tsx b/frontend/src/components/features/landing/LandingPage/AnimatedHeadline.tsx similarity index 100% rename from frontend/src/components/LandingPage/AnimatedHeadline.tsx rename to frontend/src/components/features/landing/LandingPage/AnimatedHeadline.tsx diff --git a/frontend/src/components/LandingPage/DesktopLandingView.tsx b/frontend/src/components/features/landing/LandingPage/DesktopLandingView.tsx similarity index 94% rename from frontend/src/components/LandingPage/DesktopLandingView.tsx rename to frontend/src/components/features/landing/LandingPage/DesktopLandingView.tsx index aba32fe..1902153 100644 --- a/frontend/src/components/LandingPage/DesktopLandingView.tsx +++ b/frontend/src/components/features/landing/LandingPage/DesktopLandingView.tsx @@ -1,5 +1,5 @@ import AnimatedHeadline from "./AnimatedHeadline"; -import ShoppingCartIcon from "../ShoppingListForm/ShoppingCartIcon"; +import ShoppingCartIcon from "../../shopping/ShoppingCartIcon"; import LandingContent from "./LandingContent"; import GetStartedButton from "./GetStartedButton"; diff --git a/frontend/src/components/LandingPage/GetStartedButton.tsx b/frontend/src/components/features/landing/LandingPage/GetStartedButton.tsx similarity index 100% rename from frontend/src/components/LandingPage/GetStartedButton.tsx rename to frontend/src/components/features/landing/LandingPage/GetStartedButton.tsx diff --git a/frontend/src/components/LandingPage/LandingContent.tsx b/frontend/src/components/features/landing/LandingPage/LandingContent.tsx similarity index 100% rename from frontend/src/components/LandingPage/LandingContent.tsx rename to frontend/src/components/features/landing/LandingPage/LandingContent.tsx diff --git a/frontend/src/components/LandingPage/LandingPage.tsx b/frontend/src/components/features/landing/LandingPage/LandingPage.tsx similarity index 100% rename from frontend/src/components/LandingPage/LandingPage.tsx rename to frontend/src/components/features/landing/LandingPage/LandingPage.tsx diff --git a/frontend/src/components/LandingPage/MobileLandingView.tsx b/frontend/src/components/features/landing/LandingPage/MobileLandingView.tsx similarity index 91% rename from frontend/src/components/LandingPage/MobileLandingView.tsx rename to frontend/src/components/features/landing/LandingPage/MobileLandingView.tsx index 26bd02d..b905f9d 100644 --- a/frontend/src/components/LandingPage/MobileLandingView.tsx +++ b/frontend/src/components/features/landing/LandingPage/MobileLandingView.tsx @@ -1,6 +1,6 @@ -import CardContainer from "../CardContainer"; +import CardContainer from "../../../shared/layout/CardContainer"; import AnimatedHeadline from "./AnimatedHeadline"; -import ShoppingCartIcon from "../ShoppingListForm/ShoppingCartIcon"; +import ShoppingCartIcon from "../../shopping/ShoppingCartIcon"; import LandingContent from "./LandingContent"; import GetStartedButton from "./GetStartedButton"; diff --git a/frontend/src/components/features/shopping/CustomInputToggle.tsx b/frontend/src/components/features/shopping/CustomInputToggle.tsx new file mode 100644 index 0000000..6f724a2 --- /dev/null +++ b/frontend/src/components/features/shopping/CustomInputToggle.tsx @@ -0,0 +1,64 @@ +import { motion, AnimatePresence } from "framer-motion"; +import { type CustomInputToggleProps } from "../../types"; + +export default function CustomInputToggle({ + showDetails, + onToggle, + customInput, + onInputChange, + placeholder = "e.g., 2L milk, 1kg chicken (organic), bananas (free-range), bread...", + tip = '💡 Tip: Include quantities, sizes, and use (parentheses) for notes. Example: "2L milk, 1kg chicken (organic), bananas (free-range)"', +}: CustomInputToggleProps) { + return ( +
+ {/* Toggle */} +
+ + +
+ + {/* Input Field */} + + {showDetails && ( + +
+ +