diff --git a/frontend/src/components/listings/ListingGrid.tsx b/frontend/src/components/listings/ListingGrid.tsx
index 7c0af9cb..dfab100e 100644
--- a/frontend/src/components/listings/ListingGrid.tsx
+++ b/frontend/src/components/listings/ListingGrid.tsx
@@ -1,4 +1,6 @@
import { useEffect, useState } from "react";
+import Masonry from "react-masonry-css";
+import { Link } from "react-router-dom";
import { paths } from "gen/api";
import { useAlertQueue } from "hooks/useAlertQueue";
@@ -44,20 +46,35 @@ const ListingGrid = (props: ListingGridProps) => {
}
}, [listingIds]);
+ const breakpointColumnsObj = {
+ default: 4,
+ 1536: 3,
+ 1280: 3,
+ 1024: 2,
+ 768: 2,
+ 640: 1,
+ };
+
return listingIds === null ? (
) : (
-
+
{listingIds.map((listingId) => (
- l.id === listingId)}
- />
+
+ l.id === listingId)}
+ showDescription={true}
+ />
+
))}
-
+
);
};
diff --git a/frontend/src/components/listings/ListingGridCard.tsx b/frontend/src/components/listings/ListingGridCard.tsx
index 94abba4f..01da63ff 100644
--- a/frontend/src/components/listings/ListingGridCard.tsx
+++ b/frontend/src/components/listings/ListingGridCard.tsx
@@ -1,94 +1,56 @@
-import { useState } from "react";
-import { FaEye } from "react-icons/fa";
-import { useNavigate } from "react-router-dom";
-
-import clsx from "clsx";
import { paths } from "gen/api";
-import { formatNumber } from "utils/formatNumber";
-import { formatTimeSince } from "utils/formatTimeSince";
-
-import ImagePlaceholder from "components/ImagePlaceholder";
-import ListingVoteButtons from "components/listing/ListingVoteButtons";
-import { Card, CardFooter, CardHeader, CardTitle } from "components/ui/Card";
type ListingInfo =
paths["/listings/batch"]["get"]["responses"][200]["content"]["application/json"]["listings"][0];
-interface Props {
+interface ListingGridCardProps {
listingId: string;
- listing: ListingInfo | undefined;
+ listing?: ListingInfo;
+ showDescription?: boolean;
}
-const ListingGridCard = ({ listingId, listing }: Props) => {
- const navigate = useNavigate();
- const [hovering, setHovering] = useState(false);
+const ListingGridCard = ({
+ listing,
+ showDescription,
+}: ListingGridCardProps) => {
+ const getFirstLine = (text: string | null) => {
+ if (!text) return null;
+ const firstLine = text.split("\n")[0].trim();
+ return firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
+ };
return (
- setHovering(true)}
- onMouseLeave={() => setHovering(false)}
- onClick={() => navigate(`/item/${listingId}`)}
- >
- {/* Hover overlay */}
-
-
- {listing?.image_url ? (
-
-
-
- ) : (
-
- )}
-
-
-
- {listing ? (
- listing.name
- ) : (
-
- )}
-
-
-
- {listing && (
- <>
-
-
- {formatNumber(listing.views || 0)}
-
-
- {formatTimeSince(new Date(listing.created_at * 1000))}
-
- >
+
+ {listing ? (
+ <>
+ {listing.image_url && (
+
+
+
)}
-
-
- {listing && (
-
-
+
+
+ {listing.name}
+
+ {showDescription && listing.description !== null && (
+
+ {getFirstLine(listing.description)}
+
+ )}
+
+ >
+ ) : (
+
)}
-
+
);
};
diff --git a/store/app/crud/users.py b/store/app/crud/users.py
index 97147f9e..9d7e56ac 100644
--- a/store/app/crud/users.py
+++ b/store/app/crud/users.py
@@ -47,23 +47,17 @@ async def get_user(self, id: str, throw_if_missing: bool = False) -> User | None
async def get_user(self, id: str, throw_if_missing: bool = False) -> User | None:
return await self._get_item(id, User, throw_if_missing=throw_if_missing)
- """For safely retrieving public user data for display on profile pages"""
-
async def get_user_public(self, id: str, throw_if_missing: bool = False) -> UserPublic | None:
user = await self.get_user(id, throw_if_missing=throw_if_missing)
if user is None:
return None
return UserPublic(**user.model_dump())
- """Standard sign up with email and password, leaves oauth providers empty"""
-
async def _create_user_from_email(self, email: str, password: str) -> User:
user = User.create(email=email, password=password)
await self._add_item(user, unique_fields=["email"])
return user
- """OAuth sign up, creates user and links OAuthKey"""
-
async def _create_user_from_oauth(self, email: str, provider: str, user_token: str) -> User:
user = await self.get_user_from_email(email)
if user is None:
@@ -195,9 +189,7 @@ async def update_user(self, user_id: str, updates: dict[str, Any]) -> User:
async def test_adhoc() -> None:
async with UserCrud() as crud:
await crud._create_user_from_email(email="ben@kscale.dev", password="examplepas$w0rd")
-
await crud.get_user_from_github_token(token="gh_token_example", email="oauth_github@kscale.dev")
-
await crud.get_user_from_google_token(email="oauth_google@kscale.dev")
diff --git a/store/app/routers/listings.py b/store/app/routers/listings.py
index cb9e5234..9bb1fa27 100644
--- a/store/app/routers/listings.py
+++ b/store/app/routers/listings.py
@@ -230,7 +230,7 @@ class GetListingResponse(BaseModel):
views: int
score: int
user_vote: bool | None
- creator_id: str # Add this line
+ creator_id: str
creator_name: str | None
diff --git a/store/app/routers/users.py b/store/app/routers/users.py
index 67627edb..b67eda6c 100644
--- a/store/app/routers/users.py
+++ b/store/app/routers/users.py
@@ -341,7 +341,7 @@ async def update_profile(
crud: Annotated[Crud, Depends(Crud.get)],
) -> UserPublic:
try:
- update_dict = updates.dict(exclude_unset=True, exclude_none=True)
+ update_dict = updates.model_dump(exclude_unset=True, exclude_none=True)
updated_user = await crud.update_user(user.id, update_dict)
return UserPublic(**updated_user.model_dump())
except ValueError as e: