Skip to content

Commit

Permalink
Updated Creating and build details page (#509)
Browse files Browse the repository at this point in the history
* Updated Creating and build details page

* Resolved unit tests issues

* Resolved frontend issues

* Additional frontend changes

* Frontend changes

* Resolved slugify issues

* updates
  • Loading branch information
ivntsng authored Oct 28, 2024
1 parent d140b55 commit 4f6a17f
Show file tree
Hide file tree
Showing 12 changed files with 622 additions and 248 deletions.
28 changes: 28 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"pako": "^2.1.0",
"radix-ui": "^1.0.1",
"react": "^18.0.0",
"react-bootstrap-icons": "^1.11.4",
"react-device-detect": "^2.2.3",
"react-dom": "^18.0.0",
"react-dropzone": "^14.2.3",
Expand All @@ -65,6 +66,7 @@
"react-highlight": "^0.15.0",
"react-hook-form": "^7.52.2",
"react-icons": "^5.2.1",
"react-images-uploading": "^3.1.7",
"react-intersection-observer": "^9.13.1",
"react-markdown": "^9.0.1",
"react-masonry-css": "^1.0.16",
Expand Down
235 changes: 116 additions & 119 deletions frontend/src/components/listing/ListingBody.tsx
Original file line number Diff line number Diff line change
@@ -1,143 +1,140 @@
import { useEffect, useState } from "react";
import Masonry from "react-masonry-css";
import React, { useEffect, useState } from "react";

import ListingChildren from "@/components/listing/ListingChildren";
import ListingDescription from "@/components/listing/ListingDescription";
import ListingOnshape from "@/components/listing/onshape/ListingOnshape";
import { Card, CardContent } from "@/components/ui/Card";
import { components, paths } from "@/gen/api";
import ProductPage from "@/components/products/ProductPage";
import { useAlertQueue } from "@/hooks/useAlertQueue";
import { useAuthentication } from "@/hooks/useAuth";

import ArtifactCard from "./artifacts/ArtifactCard";
import LoadingArtifactCard from "./artifacts/LoadingArtifactCard";

type ListingResponse =
paths["/listings/{id}"]["get"]["responses"][200]["content"]["application/json"];
// Update the ListingResponse type to match the actual structure
type ListingResponse = {
id: string;
name: string;
username: string | null;
slug: string | null;
description: string | null;
child_ids: string[];
tags: string[];
onshape_url: string | null;
can_edit: boolean;
created_at: number;
creator_name: string | null;
uploaded_files?: { url: string }[];
price?: number;
key_features?: string;
};

interface ListingBodyProps {
listing: ListingResponse;
newTitle?: string;
}

const ListingBody = (props: ListingBodyProps) => {
const { listing } = props;
const { addErrorAlert } = useAlertQueue();
const ListingBody: React.FC<ListingBodyProps> = ({ listing, newTitle }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [images, setImages] = useState<string[]>([]);
const auth = useAuthentication();
const [artifacts, setArtifacts] = useState<
components["schemas"]["ListArtifactsResponse"]["artifacts"] | null
>(null);

const addArtifactId = async (newArtifactId: string) => {
const { data, error } = await auth.client.GET(
"/artifacts/info/{artifact_id}",
{
params: { path: { artifact_id: newArtifactId } },
},
);

if (error) {
addErrorAlert(error);
} else {
setArtifacts((prev) => {
if (prev === null) return prev;
return [...prev, data];
});
}
};

const handleDeleteArtifact = (artifactId: string) => {
setArtifacts((prevArtifacts) =>
prevArtifacts
? prevArtifacts.filter(
(artifact) => artifact.artifact_id !== artifactId,
)
: null,
);
};

const breakpointColumnsObj = {
default: 3,
1024: 2,
640: 1,
};
const { addErrorAlert } = useAlertQueue();

useEffect(() => {
if (artifacts !== null) return;

const fetchArtifacts = async () => {
const { data, error } = await auth.client.GET(
"/artifacts/list/{listing_id}",
{
params: { path: { listing_id: listing.id } },
},
);

if (error) {
addErrorAlert(error);
} else {
setArtifacts(data.artifacts);
try {
const { data, error } = await auth.client.GET(
"/artifacts/list/{listing_id}",
{
params: { path: { listing_id: listing.id } },
},
);

if (error) {
addErrorAlert(error);
} else {
const artifactImages = data.artifacts
.filter(
(artifact: { artifact_type: string }) =>
artifact.artifact_type === "image",
)
.map(
(artifact: { urls: { large: string } }) => artifact.urls.large,
);

const uploadedImages =
listing.uploaded_files?.map((file: { url: string }) => file.url) ||
[];
setImages([...uploadedImages, ...artifactImages]);
}
} catch (err) {
addErrorAlert(
`Error fetching artifacts: ${err instanceof Error ? err.message : String(err)}`,
);
}
};

fetchArtifacts();
}, [listing.id, artifacts, auth.client, addErrorAlert]);
}, [listing.id, auth.client, addErrorAlert]);

console.log("Raw listing price:", listing.price);
console.log("Listing price type:", typeof listing.price);

const productInfo = {
name: newTitle || listing.name,
description: listing.description || "Product Description",
specs: listing.key_features ? listing.key_features.split("\n") : [],
features: [],
price: listing.price ?? 0,
productId: listing.id,
};

console.log("Product info:", productInfo);

const openModal = (image: string) => {
setSelectedImage(image);
setIsModalOpen(true);
};

const closeModal = () => {
setSelectedImage(null);
setIsModalOpen(false);
};

return (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<ListingDescription
listingId={listing.id}
description={listing.description}
edit={listing.can_edit}
/>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<ListingOnshape
listingId={listing.id}
onshapeUrl={listing.onshape_url}
addArtifactId={addArtifactId}
edit={listing.can_edit}
/>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<ListingChildren
child_ids={listing.child_ids}
edit={listing.can_edit}
/>
</CardContent>
</Card>
<div className="mt-4">
<Masonry
breakpointCols={breakpointColumnsObj}
className="flex w-auto -ml-4"
columnClassName="pl-4 bg-clip-padding"
>
{artifacts === null ? (
<LoadingArtifactCard />
) : (
artifacts
.slice()
.reverse()
.map((artifact) => (
<Card key={artifact.artifact_id} className="mb-4">
<CardContent className="p-4">
<ArtifactCard
artifact={artifact}
onDelete={() =>
handleDeleteArtifact(artifact.artifact_id)
}
canEdit={listing.can_edit}
/>
</CardContent>
</Card>
))
)}
</Masonry>
<ProductPage
title={productInfo.name}
images={images}
productId={productInfo.productId}
checkoutLabel={`Buy ${productInfo.name}`}
description={productInfo.description}
features={productInfo.features}
keyFeatures={productInfo.specs}
price={productInfo.price}
onImageClick={openModal}
/>
<div className="mt-6">
<ListingOnshape
listingId={listing.id}
onshapeUrl={listing.onshape_url}
addArtifactId={async () => {}}
edit={listing.can_edit}
/>
</div>

{isModalOpen && selectedImage && (
<div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
<div className="relative">
<img
src={selectedImage}
alt="Selected"
className="max-w-full max-h-full"
/>
<button
onClick={closeModal}
className="absolute top-0 right-0 m-4 text-white text-2xl"
>
&times;
</button>
</div>
</div>
)}
</div>
);
};
Expand Down
Loading

0 comments on commit 4f6a17f

Please sign in to comment.