Refactor asset management components to enhance structure and improve data handling; update styles for better UI consistency

This commit is contained in:
Vishnu 2025-05-13 15:18:15 +05:30
parent 4c13d31931
commit 4939c19c12
8 changed files with 102 additions and 83 deletions

View File

@ -1,7 +1,6 @@
import React, { Suspense, useEffect } from "react";
import assetImage from "../../assets/image/image.png";
import React, { Suspense } from "react";
import { FilledStarsIconSmall } from "../../components/icons/marketPlaceIcons";
import { Canvas, useThree } from "@react-three/fiber";
import { Canvas } from "@react-three/fiber";
import { ContactShadows, OrbitControls, Text } from "@react-three/drei";
import GltfLoader from "./GltfLoader";
import * as THREE from "three";
@ -14,17 +13,26 @@ interface SelectedCard {
rating: number;
views: number;
description: string;
AssetID: string;
}
// Define the props type for AssetPreview
interface AssetPreviewProps {
selectedCard: SelectedCard;
modelUrl: string;
setSelectedCard: React.Dispatch<React.SetStateAction<SelectedCard | null>>; // Type for setter function
}
const savedTheme: string | null = localStorage.getItem("theme");
function Ui() {
return (
<Text color="#6f42c1" anchorX="center" anchorY="middle" scale={0.3}>
<Text
color={savedTheme === "dark" ? "#d2baff" : "#6f42c1"}
anchorX="center"
anchorY="middle"
scale={0.3}
>
Loading preview...
</Text>
);
@ -33,16 +41,8 @@ function Ui() {
const AssetPreview: React.FC<AssetPreviewProps> = ({
selectedCard,
setSelectedCard,
modelUrl,
}) => {
// Ensure rating is a valid number between 0 and 5
const rating = Math.max(
0,
Math.min(5, isNaN(selectedCard.rating) ? 0 : selectedCard.rating)
);
// Ensure that the rating is a valid positive integer for array length
const starsArray = Array.from({ length: rating }, (_, index) => index);
return (
<div className="assetPreview-wrapper">
<div className="assetPreview">
@ -53,18 +53,19 @@ const AssetPreview: React.FC<AssetPreviewProps> = ({
<Canvas
flat
shadows
color="#FFFFFF"
camera={{ fov: 75 }}
gl={{
preserveDrawingBuffer: true,
}}
onCreated={({ scene }) => {
scene.background = new THREE.Color(0xffffff);
scene.background = new THREE.Color(
savedTheme === "dark" ? 0x19191d : 0xfcfdfd
);
}}
>
<Suspense fallback={<Ui />}>
{selectedCard.assetName && (
<GltfLoader fromServer={selectedCard.assetName} />
{selectedCard.assetName && modelUrl && (
<GltfLoader fromServer={modelUrl} />
)}
<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI / 2} />
<ContactShadows
@ -113,9 +114,9 @@ const AssetPreview: React.FC<AssetPreviewProps> = ({
</div>
{/* close button */}
<div className="closeButton" onClick={() => setSelectedCard(null)}>
<button className="closeButton" onClick={() => setSelectedCard(null)}>
{`<-back`}
</div>
</button>
</div>
</div>
</div>

View File

@ -1,15 +1,12 @@
import React, { useEffect } from "react";
import React from "react";
import {
CommentsIcon,
DownloadIcon,
EyeIconBig,
FilledStarsIconSmall,
StarsIconSmall,
VerifiedIcon,
} from "../../components/icons/marketPlaceIcons";
import assetImage from "../../assets/image/image.png";
import { getAssetDownload } from "../../services/marketplace/getAssetDownload";
interface CardProps {
assetName: string;
@ -19,6 +16,7 @@ interface CardProps {
views: number;
image: string;
description: string;
AssetID: string;
onSelectCard: (cardData: {
assetName: string;
uploadedOn: number;
@ -26,6 +24,7 @@ interface CardProps {
rating: number;
views: number;
description: string;
AssetID: string;
}) => void;
}
@ -38,9 +37,18 @@ const Card: React.FC<CardProps> = ({
image,
description,
onSelectCard,
AssetID,
}) => {
const handleCardSelect = () => {
onSelectCard({ assetName, uploadedOn, price, rating, views, description });
onSelectCard({
assetName,
uploadedOn,
price,
rating,
views,
description,
AssetID,
});
};
return (
@ -81,22 +89,22 @@ const Card: React.FC<CardProps> = ({
<div className="stars-container">
<div className="stars-wrapper">
{[...Array(5)].map((_, index) => (
<>
<React.Fragment key={index} >
{index < 3 ? (
<FilledStarsIconSmall key={index} />
<FilledStarsIconSmall />
) : (
<StarsIconSmall key={index} />
<StarsIconSmall />
)}
</>
</React.Fragment>
))}
</div>
<div className="units">
{price}/<span>unit</span>
</div>
</div>
<div className="buy-now-button" onClick={handleCardSelect}>
<button className="buy-now-button" onClick={handleCardSelect}>
Buy now
</div>
</button>
</div>
);
};

View File

@ -1,6 +1,7 @@
import React, { useState } from "react";
import Card from "./Card";
import AssetPreview from "./AssetPreview";
import { fetchGltfUrl } from "../../services/marketplace/fetchGltfUrl";
interface ModelData {
CreatedBy: string;
@ -15,12 +16,14 @@ interface ModelData {
uploadDate: number;
_id: string;
price: number;
AssetID: string;
}
interface ModelsProps {
models: ModelData[];
}
const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
const [modelUrl, setModelUrl] = useState<string>("");
const [selectedCard, setSelectedCard] = useState<{
assetName: string;
uploadedOn: number;
@ -28,19 +31,23 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
rating: number;
views: number;
description: string;
AssetID: string;
} | null>(null);
const handleCardSelect = (cardData: {
const handleCardSelect = async (cardData: {
assetName: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
description: string;
AssetID: string;
}) => {
setSelectedCard(cardData);
const res = await fetchGltfUrl(cardData.assetName, cardData.AssetID);
console.log("res: ", res);
setModelUrl(res.url);
};
return (
<div className="cards-container-wrapper">
<div className="cards-container-container">
@ -56,6 +63,7 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
rating={4.5}
views={800}
onSelectCard={handleCardSelect}
AssetID={assetDetail.AssetID}
image={assetDetail.thumbnail}
description={assetDetail.description}
/>
@ -66,6 +74,7 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
<AssetPreview
selectedCard={selectedCard}
setSelectedCard={setSelectedCard}
modelUrl={modelUrl}
/>
)}
{/* </RenderOverlay> */}

View File

@ -16,6 +16,7 @@ interface ModelData {
uploadDate: number;
_id: string;
price: number;
AssetID: string;
}
interface ModelsProps {
@ -44,7 +45,7 @@ const FilterSearch: React.FC<ModelsProps> = ({
const descending = [...models].sort((a, b) => b.filename.localeCompare(a.filename));
setModels(descending);
}
}, [activeOption]);
}, [activeOption, models, setModels]);
const handleSearch = (val: string) => {
const filteredModel = filteredModels.filter((model) =>
@ -73,13 +74,13 @@ const FilterSearch: React.FC<ModelsProps> = ({
<div className="label">Rating</div>
<div className="stars">
{[0, 1, 2, 3, 4].map((i) => (
<div
<button
key={i}
onClick={() => handleStarClick(i)}
className={`star-wrapper ${i < rating ? "filled" : "empty"}`}
>
<StarsIcon />
</div>
</button>
))}
</div>
</div>

View File

@ -3,7 +3,6 @@ import { useFrame } from "@react-three/fiber";
import { Stage, useGLTF } from "@react-three/drei";
import { AnimationMixer, AnimationAction, Object3D } from "three";
import * as THREE from "three";
import { fetchGltfUrl } from "../../services/marketplace/fetchGltfUrl";
interface GltfLoaderProps {
glbdata?: boolean;
@ -13,7 +12,6 @@ interface GltfLoaderProps {
setSelectedAnimation?: (animation: string) => void;
}
// const getGLTFUrl = (url: string) => url; // Placeholder for your actual function
const GltfLoader: React.FC<GltfLoaderProps> = ({
glbdata,
@ -21,12 +19,10 @@ const GltfLoader: React.FC<GltfLoaderProps> = ({
setAnimations,
selectedAnimation,
}) => {
const modelUrl: any = fromServer ? fetchGltfUrl(fromServer) : glbdata;
const { scene, animations } = useGLTF(modelUrl ?? "") as {
const { scene, animations } = useGLTF(fromServer ?? "") as {
scene: Object3D;
animations: THREE.AnimationClip[];
};
const mixer = useRef<AnimationMixer | null>(
scene ? new AnimationMixer(scene) : null
);

View File

@ -16,6 +16,7 @@ interface ModelData {
uploadDate: number;
_id: string;
price: number;
AssetID: string;
}
const MarketPlace = () => {
const [models, setModels] = useState<ModelData[]>([]);

View File

@ -1,7 +1,26 @@
let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
export const fetchGltfUrl = (filename: string) => {
if (filename) {
return `${BackEnd_url}/api/v1/getAssetFile/${filename}`;
export const fetchGltfUrl = async (filename: string, AssetID: string) => {
try {
const response = await fetch(
`${BackEnd_url}/api/v2/assetDetails/${filename}/${AssetID}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
// body: JSON.stringify(assetData),
}
);
if (!response.ok) {
throw new Error("Failed to fetch asset details");
}
const result = await response.json();
return result;
} catch (error: any) {
//
throw new Error(error.message);
}
return null; // or handle the case when filename is not provided
};
}

View File

@ -2,9 +2,6 @@
@use "../../abstracts/mixins.scss" as *;
.marketplace-wrapper {
// transform: scale(0.65);
/* Start at 90% width */
height: 100vh;
width: 100vw;
z-index: #{$z-index-marketplace};
@ -14,7 +11,6 @@
top: 0;
padding: 10px;
padding-top: 100px;
// animation: growWidth 0.4s ease-in-out 0.5s forwards;
.marketplace-container {
position: relative;
@ -43,8 +39,6 @@
width: 100%;
.skeleton-content {
width: calc(25% - 14px) !important;
height: 100%;
border-radius: #{$border-radius-xlarge};
@ -79,7 +73,6 @@
}
.button {
width: 100%;
height: 35px;
border-radius: 20px;
@ -143,8 +136,7 @@
.star-wrapper.filled {
svg {
fill: #F3A50C;
fill: #f3a50c;
}
}
}
@ -190,10 +182,6 @@
justify-content: center;
gap: 6px;
.assets-container {
height: auto;
}
.icon {
position: absolute;
top: 12px;
@ -208,7 +196,7 @@
.image-container {
width: 100%;
display: flex;
max-height: 180px;
height: 180px;
justify-content: center;
border-radius: #{$border-radius-medium};
overflow: hidden;
@ -224,7 +212,7 @@
display: flex;
justify-content: space-between;
padding: 0;
height: auto;
.name-container {
display: flex;
flex-direction: column;
@ -299,7 +287,6 @@
}
}
.assetPreview-wrapper {
width: 100%;
height: 100%;
@ -307,15 +294,18 @@
top: 0;
left: 0;
z-index: 3;
padding: 0 10px;
.assetPreview {
width: 100%;
height: 100%;
background: var(--background-color);
backdrop-filter: blur(18px);
display: flex;
gap: 12px;
overflow: hidden;
border-radius: #{$border-radius-extra-large};
outline: 1px solid var(--border-color);
}
// Image Preview Section
@ -352,8 +342,8 @@
min-width: 26px;
border-radius: #{$border-radius-circle};
font-weight: var(--font-weight-bold);
color: var(--background-color);
background: var(--accent-color);
color: var(--text-button-color);
background: var(--background-color-button);
}
.organization-details {
@ -361,9 +351,7 @@
flex-direction: column;
.organization-name {
font-weight: bold;
margin-bottom: 5px;
font-weight: #{$bold-weight};
font-size: $regular;
}
@ -380,35 +368,31 @@
margin-top: 20px;
.asset-name {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 10px;
font-weight: #{$bold-weight};
font-size: $large;
font-size: var(--font-size-large);
}
.asset-description {
margin-bottom: 20px;
color: #666;
color: var(--input-text-color);
}
.asset-review {
width: fit-content;
padding: 5px 10px;
padding: 5px 14px;
display: flex;
align-items: center;
margin-bottom: 20px;
outline: 1px solid #909090cc;
border-radius: #{$border-radius-small};
outline: 1px solid var(--border-color);
border-radius: #{$border-radius-large};
.asset-rating {
display: flex;
align-items: center;
gap: 4px;
margin-right: 10px;
font-weight: bold;
position: relative;
font-weight: #{$bold-weight};
font-size: $regular;
@ -442,25 +426,25 @@
}
.button {
color: white;
padding: 10px 20px;
border-radius: #{$border-radius-small};
color: var(--text-button-color);
padding: 8px 26px;
border-radius: #{$border-radius-extra-large};
cursor: pointer;
text-align: center;
&:first-child {
outline: 1px solid var(--accent-color);
color: var(--accent-color);
outline: 1px solid var(--background-color-button);
color: var(--highlight-text-color);
}
&:last-child {
background: var(--accent-color);
color: var(--background-color);
background: var(--background-color-button);
color: var(--text-button-color);
}
}
.closeButton {
color: var(--accent-color);
color: var(--highlight-text-color);
position: absolute;
top: 18px;
left: 18px;
@ -468,4 +452,4 @@
cursor: pointer;
font-size: var(--font-size-large);
}
}
}