Repository: dicarlosofia/ReuseU
Pull request #79: cleaner frontend - better listings layout, new dropdown, etc.
Author: login: howieyoungdahl
Created: 2025-04-02T15:41:47Z | Merged: 2025-04-03T00:50:46Z

New dropdown implementation based off of paper mockup - redid listings design to fit new layout. 


Diff:
diff --git a/frontend/src/components/Dropdown/Dropdown.tsx b/frontend/src/components/Dropdown/Dropdown.tsx
new file mode 100644
index 0000000..ec01c41
--- /dev/null
+++ b/frontend/src/components/Dropdown/Dropdown.tsx
@@ -0,0 +1,67 @@
+import { useState } from 'react';
+
+interface Props {
+  onCategorySelect?: (category: string) => void;
+}
+
+export const Dropdown: React.FC<Props> = ({ onCategorySelect }) => {
+  const [expandedCategories, setExpandedCategories] = useState<number[]>([]);
+
+  const toggleCategory = (index: number) => {
+    setExpandedCategories(prev => 
+      prev.includes(index) 
+        ? prev.filter(i => i !== index) 
+        : [...prev, index]
+    );
+  };
+
+  const categories = [
+    { name: 'Price', items: ["Under $10", "$10 - $50", "$50 - $100", "$100 - $500", "Above $500"] },
+    { name: 'Kitchen', items: ["Cookware", "Appliances", "Utensils", "Storage", "Dinnerware"] },
+    { name: 'Furniture', items: ["Chairs", "Tables", "Beds", "Desks", "Storage"] },
+    { name: 'Electronics', items: ["Laptops", "Phones", "Tablets", "TVs", "Audio"] },
+    { name: 'Clothing', items: ["Shirts", "Tops", "Bottoms", "Dresses", "Accessories"] },
+    { name: 'Miscellaneous', items: ["Books", "Toys", "Art", "Crafts", "Other"  ] }
+  ];
+
+  return (
+    <div className="h-full flex flex-col gap-6 py-4">
+      {categories.map((category, index) => (
+        <div key={index} className="px-4">
+          <div 
+            onClick={() => toggleCategory(index)}
+            className="flex items-center justify-between cursor-pointer hover:bg-gray-50 p-3 rounded-lg transition-colors"
+          >
+            <span className="text-lg font-medium">{category.name}</span>
+            <svg 
+              className={`w-5 h-5 transition-transform duration-200 ${
+                expandedCategories.includes(index) ? 'rotate-180' : ''
+              }`} 
+              fill="none" 
+              stroke="currentColor" 
+              viewBox="0 0 24 24"
+            >
+              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
+            </svg>
+          </div>
+          <div 
+            className={`pl-6 mt-2 overflow-hidden transition-all duration-200 ${
+              expandedCategories.includes(index) 
+                ? 'max-h-[500px] opacity-100' 
+                : 'max-h-0 opacity-0'
+            }`}
+          >
+            {category.items.map((item, idx) => (
+              <div key={idx} className="py-2">
+                <label className="flex items-center gap-2 cursor-pointer">
+                  <input type="checkbox" className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
+                  <span className="text-gray-700">{item}</span>
+                </label>
+              </div>
+            ))}
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/frontend/src/components/Listings/Listing.tsx b/frontend/src/components/Listings/Listing.tsx
index 08dc063..036625e 100644
--- a/frontend/src/components/Listings/Listing.tsx
+++ b/frontend/src/components/Listings/Listing.tsx
@@ -1,15 +1,16 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
 }
 
-export default function Listing({ title, price, tags }: ListingProps) {
+export default function Listing({ title, price, tags, desc, image }: ListingProps) {
   const [isFavorited, setIsFavorited] = useState(false);
 
   const onFavoriteClick = () => {
@@ -17,33 +18,42 @@ export default function Listing({ title, price, tags }: ListingProps) {
   }
 
   return (
-    //overall listing holder
-    <div className="h-56 flex flex-row items-center justify-start text-center p-4 border rounded-lg gap-5">
-      <div className="h-full w-2/9 border-2 border-double rounded-lg">
-        {/*image goes here*/}
-      </div>
-      <div className="h-full w-5/9 flex flex-col border-2 border-dotted rounded-lg gap-[1vh] justify-center">
-        <div className ="h-1/8 border-2 border-double rounded-lg">
-          <h3 className="font-bold">Title</h3>
-        </div>
-        <div className ="h-1/4 border-2 border-doublerounded-lg">
-          <text className="self-start ml-[1vh]">
-            This is where my tags will go
-          </text>
-        </div>
-        <div className ="h-1/2 border-2 border-double rounded-lg">
-        <text className="self-start ml-[1vh]">
-            Bruh stinky stinky stinky bruh bruh stinky stinky stinky bruh bruh stink
-          </text>
-        </div>
+    <div className="flex flex-row bg-white rounded-lg shadow-sm border p-4 gap-4 h-[180px]">
+      {/* Image container */}
+      <div className="w-1/4 aspect-square bg-gray-100 rounded-lg">
+        {image && <img src={image} alt={title} className="w-full h-full object-cover rounded-lg" />}
       </div>
+
+      {/* Content container */}
+      <div className="flex-1 flex flex-col min-w-0">
+        <h3 className="text-lg font-semibold line-clamp-1 mb-2">{title}</h3>
         
-      <div className="h-full w-2/9 rounded-lg flex flex-col rounded-lg justify-between border-2 border-double">
-        <div className="">
-          $19.99
+        {/* Tags */}
+        <div className="flex flex-wrap gap-1.5 mb-2">
+          {tags.map((tag, index) => (
+            <span key={index} className="px-2 py-0.5 bg-gray-100 rounded text-sm text-gray-600">
+              {tag}
+            </span>
+          ))}
         </div>
-        {isFavorited && <HeartOutline onClick={onFavoriteClick} className="cursor-pointer h-1/4 -mr-[12vh] fill-white"></HeartOutline>}
-        {!isFavorited && <HeartSolid onClick={onFavoriteClick} className="cursor-pointer h-1/4 -mr-[12vh] fill-red-500"></HeartSolid>}
+
+        {/* Description */}
+        <p className="text-gray-600 text-sm line-clamp-2 flex-grow">{desc}</p>
+      </div>
+
+      {/* Price and favorite section */}
+      <div className="w-20 flex flex-col items-end justify-between">
+        <div className="text-lg font-bold">${price.toFixed(2)}</div>
+        <button 
+          onClick={onFavoriteClick}
+          className="p-1.5 hover:bg-gray-100 rounded-full transition-colors"
+        >
+          {isFavorited ? (
+            <HeartSolid className="w-5 h-5 text-red-500" />
+          ) : (
+            <HeartOutline className="w-5 h-5" />
+          )}
+        </button>
       </div>
     </div>
   );
diff --git a/frontend/src/components/Listings/ListingsHomepage.tsx b/frontend/src/components/Listings/ListingsHomepage.tsx
index ea1b182..e544797 100644
--- a/frontend/src/components/Listings/ListingsHomepage.tsx
+++ b/frontend/src/components/Listings/ListingsHomepage.tsx
@@ -1,14 +1,69 @@
 import Listing from "./Listing"
+import { Dropdown } from "../Dropdown/Dropdown"
 
+const SAMPLE_LISTINGS = [
+  {
+    title: "Air Force 1s",
+    price: 20.00,
+    tags: ["Clothing", "Shoes"],
+    desc: "These are cool!",
+    image: ""
+  },
+  {
+    title: "Dell Laptop",
+    price: 200.00,
+    tags: ["Electronic", "Device"],
+    desc: "Super fast, 5 years old...",
+    image: ""
+  },
+  {
+    title: "Chess Set",
+    price: 50.00,
+    tags: ["Furniture", "New"],
+    desc: "Made of wood!",
+    image: ""
+  },
+  {
+    title: "Pet Rock",
+    price: 1.69,
+    tags: ["Furniture"],
+    desc: "",
+    image: ""
+  }
+];
 
 export default function ListingsHomepage() {
-    return (
-      <div className="w-11/12 mx-auto mt-10 p-4">
-        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
-          <Listing title={"Deadbeat Father"} price={399} tags={["used"]}/>
-          <Listing title={"Deadbeat Father"} price={399} tags={["used"]}/>
-          <Listing title={"Deadbeat Father"} price={399} tags={["used"]}/>
+  return (
+    <div className="min-h-screen pt-6">
+      <div className="flex gap-8">
+        {/* Sidebar */}
+        <div className="w-64 shrink-0 pl-2">
+          <div className="sticky top-24 bg-white rounded-lg shadow-sm h-[calc(100vh-8rem)]">
+            <div className="p-6 border-b">
+              <h2 className="text-2xl font-semibold text-center">Filters</h2>
+            </div>
+            <div className="p-4 h-full">
+              <Dropdown />
+            </div>
+          </div>
+        </div>
+
+        {/* Main content */}
+        <div className="flex-1 py-2 pr-4 max-w-[1400px]">
+          <div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
+            {SAMPLE_LISTINGS.map((listing, index) => (
+              <Listing
+                key={index}
+                title={listing.title}
+                price={listing.price}
+                tags={listing.tags}
+                desc={listing.desc}
+                image={listing.image}
+              />
+            ))}
+          </div>
         </div>
       </div>
-    );
-  }
\ No newline at end of file
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/frontend/src/components/Listings/Tag.tsx b/frontend/src/components/Listings/Tag.tsx
deleted file mode 100644
index 92c3a1d..0000000
--- a/frontend/src/components/Listings/Tag.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-
-const Tag = (tagsList: string[]) => {
-
-    return (
-        <>
-        Bruh
-        </>
-    )
-
-}
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index a21960f..7a1462a 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -15,11 +15,13 @@ const geistMono = Geist_Mono({
 
 export default function Home() {
   return (
-    <div className="w-full min-h-screen bg-[#EEDBBE]">
-      <Dashboard/>
-      <div className="pt-16">
-        <ListingsHomepage/>
+    <main className={`${geistSans.variable} ${geistMono.variable} font-sans`}>
+      <div className="min-h-screen bg-[#EEDBBE]">
+        <Dashboard />
+        <div className="pt-16">
+          <ListingsHomepage />
+        </div>
       </div>
-    </div>
+    </main>
   );
 }


Reviews:
user: login: timurka43
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T16:07:18Z
user: login: howieyoungdahl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:48:46Z
user: login: timurka43
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:48:49Z
user: login: howieyoungdahl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:49:03Z
user: login: howieyoungdahl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:49:16Z
user: login: howieyoungdahl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:49:23Z
user: login: howieyoungdahl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:49:40Z
user: login: timurka43
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-03T00:50:16Z

Review comments:
diff_hunk: @@ -1,49 +1,59 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
path: frontend/src/components/Listings/Listing.tsx
user: login: timurka43
body: image is a string type? Also what does the question mark after image do?
created_at: 2025-04-02T15:57:22Z
author_association: COLLABORATOR
diff_hunk: @@ -1,49 +1,59 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
 }
 
-export default function Listing({ title, price, tags }: ListingProps) {
+export default function Listing({ title, price, tags, desc, image }: ListingProps) {
path: frontend/src/components/Listings/Listing.tsx
user: login: timurka43
body: how can we modify the components later when we will work with a seller's perspective? That will also have to include buttons for editing/removing a listing? Should we split create a separate function "SellersListing"?
created_at: 2025-04-02T16:01:49Z
author_association: COLLABORATOR
path: frontend/src/components/Listings/Listing.tsx
user: login: timurka43
body: this file looks great overall, thanks for putting clear tags on each compoment in the returned markup. That will be easy to identity in the future if we need to change/modify anything
created_at: 2025-04-02T16:03:20Z
author_association: COLLABORATOR
path: frontend/src/components/Listings/Listing.tsx
user: login: timurka43
body: see some other questions below on specific lines though
created_at: 2025-04-02T16:03:36Z
author_association: COLLABORATOR
diff_hunk: @@ -1,14 +1,69 @@
 import Listing from "./Listing"
+import { Dropdown } from "../Dropdown/Dropdown"
 
+const SAMPLE_LISTINGS = [
path: frontend/src/components/Listings/ListingsHomepage.tsx
user: login: timurka43
body: these sample listings are cool. Can we set up a similar sample listing that pulls from a record in the database?
created_at: 2025-04-02T16:06:04Z
author_association: COLLABORATOR
diff_hunk: @@ -1,49 +1,59 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
path: frontend/src/components/Listings/Listing.tsx
user: login: howieyoungdahl
body: images encoded as base64 string? Don't really know how to do yet
created_at: 2025-04-03T00:48:46Z
author_association: COLLABORATOR
diff_hunk: @@ -1,14 +1,69 @@
 import Listing from "./Listing"
+import { Dropdown } from "../Dropdown/Dropdown"
 
+const SAMPLE_LISTINGS = [
path: frontend/src/components/Listings/ListingsHomepage.tsx
user: login: timurka43
body: this can be a later commit though
created_at: 2025-04-03T00:48:49Z
author_association: COLLABORATOR
diff_hunk: @@ -1,49 +1,59 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
path: frontend/src/components/Listings/Listing.tsx
user: login: howieyoungdahl
body: image? means optional input
created_at: 2025-04-03T00:49:03Z
author_association: COLLABORATOR
diff_hunk: @@ -1,49 +1,59 @@
 import { useState } from "react";
 import { HeartIcon as HeartOutline } from "@heroicons/react/24/outline";
 import { HeartIcon as HeartSolid } from "@heroicons/react/24/solid";
-import { HeartIcon } from "@heroicons/react/16/solid";
 
 interface ListingProps {
   title: string;
   price: number;
   tags: string[];
+  desc: string;
+  image?: string;
 }
 
-export default function Listing({ title, price, tags }: ListingProps) {
+export default function Listing({ title, price, tags, desc, image }: ListingProps) {
path: frontend/src/components/Listings/Listing.tsx
user: login: howieyoungdahl
body: We will have a different Listings component for such
created_at: 2025-04-03T00:49:16Z
author_association: COLLABORATOR
path: frontend/src/components/Listings/Listing.tsx
user: login: howieyoungdahl
body: Thanks!

created_at: 2025-04-03T00:49:23Z
author_association: COLLABORATOR
diff_hunk: @@ -1,14 +1,69 @@
 import Listing from "./Listing"
+import { Dropdown } from "../Dropdown/Dropdown"
 
+const SAMPLE_LISTINGS = [
path: frontend/src/components/Listings/ListingsHomepage.tsx
user: login: howieyoungdahl
body: Upon backend uploading will be first thing I do!
created_at: 2025-04-03T00:49:39Z
author_association: COLLABORATOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-02T15:42:05Z
body: [vc]: #+ojGFToDYyB0XGI6agJxOV9eUV51+BySMNDn8rCJOJ4=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJyZXVzZS11IiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc29maWFkaWNhcmxvcy1wcm9qZWN0cy9yZXVzZS11L0VLYXY0UkxvWFlwNFkyR0t4VW5TTXRTbUtDYm0iLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IkZBSUxFRCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **reuse-u** | ❌ Failed ([Inspect](https://vercel.com/sofiadicarlos-projects/reuse-u/EKav4RLoXYp4Y2GKxUnSMtSmKCbm)) |  |  | Apr 2, 2025 3:42pm |


author_association: NONE
user: login: howieyoungdahl
created_at: 2025-04-03T00:50:06Z
body: all comments resolved, merge allowed
author_association: COLLABORATOR