first commit
This commit is contained in:
133
app/src/modules/market/AssetPreview.tsx
Normal file
133
app/src/modules/market/AssetPreview.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { FilledStarsIconSmall } from "../../components/icons/marketPlaceIcons";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { ContactShadows, OrbitControls, Text } from "@react-three/drei";
|
||||
import GltfLoader from "./GltfLoader";
|
||||
import * as THREE from "three";
|
||||
|
||||
// Define the shape of the selected card
|
||||
interface SelectedCard {
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
price: number;
|
||||
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={savedTheme === "dark" ? "#d2baff" : "#6f42c1"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
scale={0.3}
|
||||
>
|
||||
Loading preview...
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const AssetPreview: React.FC<AssetPreviewProps> = ({
|
||||
selectedCard,
|
||||
setSelectedCard,
|
||||
modelUrl,
|
||||
}) => {
|
||||
return (
|
||||
<div className="assetPreview-wrapper">
|
||||
<div className="assetPreview">
|
||||
<div className="image-preview">
|
||||
{/* <img src={assetImage} alt="" /> */}
|
||||
{/* Add canvas here */}
|
||||
<div className="canvas-container" style={{ height: "100%" }}>
|
||||
<Canvas
|
||||
flat
|
||||
shadows
|
||||
camera={{ fov: 75 }}
|
||||
gl={{
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
onCreated={({ scene }) => {
|
||||
scene.background = new THREE.Color(
|
||||
savedTheme === "dark" ? 0x19191d : 0xfcfdfd
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<Ui />}>
|
||||
{selectedCard.assetName && modelUrl && (
|
||||
<GltfLoader
|
||||
fromServer={modelUrl}
|
||||
assetId={selectedCard?.AssetID}
|
||||
/>
|
||||
)}
|
||||
<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI / 2} />
|
||||
<ContactShadows
|
||||
renderOrder={2}
|
||||
frames={1}
|
||||
resolution={1024}
|
||||
scale={120}
|
||||
blur={2}
|
||||
opacity={0.4}
|
||||
far={100}
|
||||
/>
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="asset-details-preview">
|
||||
<div className="organization">
|
||||
<div className="image">H</div>
|
||||
<div className="organization-details">
|
||||
<div className="organization-name">HERX FACTORY</div>
|
||||
<div className="follow">Follow +</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* asset details */}
|
||||
<div className="asset-details">
|
||||
<div className="asset-name">{selectedCard.assetName}</div>
|
||||
<div className="asset-description">
|
||||
{`${selectedCard.description}`}
|
||||
</div>
|
||||
<div className="asset-review">
|
||||
<div className="asset-rating">
|
||||
<FilledStarsIconSmall />
|
||||
{selectedCard.rating}
|
||||
</div>
|
||||
<div className="asset-view">{selectedCard.views} views</div>
|
||||
</div>
|
||||
<div className="asset-price">₹ {selectedCard.price}</div>
|
||||
</div>
|
||||
|
||||
{/* buttons */}
|
||||
<div className="button-container">
|
||||
<div className="button">Add to cart</div>
|
||||
<div className="button">Buy now</div>
|
||||
</div>
|
||||
|
||||
{/* close button */}
|
||||
<button
|
||||
id="asset-back-buttton"
|
||||
className="closeButton"
|
||||
onClick={() => setSelectedCard(null)}
|
||||
>
|
||||
{`<-back`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetPreview;
|
||||
115
app/src/modules/market/Card.tsx
Normal file
115
app/src/modules/market/Card.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
import {
|
||||
CommentsIcon,
|
||||
EyeIconBig,
|
||||
FilledStarsIconSmall,
|
||||
StarsIconSmall,
|
||||
VerifiedIcon,
|
||||
} from "../../components/icons/marketPlaceIcons";
|
||||
|
||||
interface CardProps {
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
price: number;
|
||||
rating: number;
|
||||
views: number;
|
||||
image: string;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
modelUrl: string;
|
||||
onSelectCard: (cardData: {
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
price: number;
|
||||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const Card: React.FC<CardProps> = ({
|
||||
assetName,
|
||||
uploadedOn,
|
||||
price,
|
||||
rating,
|
||||
views,
|
||||
image,
|
||||
description,
|
||||
AssetID,
|
||||
onSelectCard,
|
||||
}) => {
|
||||
const handleCardSelect = () => {
|
||||
console.log("assetName: ", assetName);
|
||||
console.log("AssetID: ", AssetID);
|
||||
|
||||
onSelectCard({
|
||||
assetName,
|
||||
uploadedOn,
|
||||
price,
|
||||
rating,
|
||||
views,
|
||||
description,
|
||||
AssetID,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card-container">
|
||||
{/* <a href={getAssetDownload(assetName)} download className="icon">
|
||||
<DownloadIcon />
|
||||
</a> */}
|
||||
<div className="image-container">
|
||||
<img src={image} alt={assetName} />
|
||||
</div>
|
||||
<div className="assets-container">
|
||||
<div className="name-container">
|
||||
<div className="assets-name">{assetName.split("_").join(" ")}</div>
|
||||
<div className="assets-date">
|
||||
Uploaded on -{" "}
|
||||
{new Date(uploadedOn).toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="details">
|
||||
<div className="content">
|
||||
<EyeIconBig />
|
||||
{views}
|
||||
</div>
|
||||
<div className="content">
|
||||
<CommentsIcon />
|
||||
32
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="vendor-icon">
|
||||
HEXR FACTORY <VerifiedIcon />
|
||||
</div>
|
||||
<div className="stars-container">
|
||||
<div className="stars-wrapper">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{index < 3 ? <FilledStarsIconSmall /> : <StarsIconSmall />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div className="units">
|
||||
₹ {price}/<span>unit</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
id={`${AssetID}-asset-buy`}
|
||||
className="buy-now-button"
|
||||
onClick={handleCardSelect}
|
||||
>
|
||||
Buy now
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Card;
|
||||
88
app/src/modules/market/CardsContainer.tsx
Normal file
88
app/src/modules/market/CardsContainer.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from "react";
|
||||
import Card from "./Card";
|
||||
import AssetPreview from "./AssetPreview";
|
||||
import { fetchGltfUrl } from "../../services/marketplace/fetchGltfUrl";
|
||||
|
||||
interface ModelData {
|
||||
CreatedBy: string;
|
||||
animated: string | null;
|
||||
category: string;
|
||||
description: string;
|
||||
filename: string;
|
||||
isArchieve: boolean;
|
||||
modelfileID: string;
|
||||
tags: string;
|
||||
thumbnail: string;
|
||||
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;
|
||||
price: number;
|
||||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
} | null>(null);
|
||||
|
||||
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">
|
||||
<div className="header">Products You May Like</div>
|
||||
<div className="cards-wrapper-container">
|
||||
{models.length > 0 &&
|
||||
models.map((assetDetail) => (
|
||||
<React.Fragment key={assetDetail._id}>
|
||||
<Card
|
||||
assetName={assetDetail?.filename}
|
||||
uploadedOn={assetDetail.uploadDate}
|
||||
price={assetDetail?.price}
|
||||
rating={4.5}
|
||||
views={800}
|
||||
onSelectCard={handleCardSelect}
|
||||
AssetID={assetDetail.AssetID}
|
||||
image={assetDetail.thumbnail}
|
||||
modelUrl={assetDetail.modelfileID}
|
||||
description={assetDetail.description}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{/* <RenderOverlay> */}
|
||||
{selectedCard && (
|
||||
<AssetPreview
|
||||
selectedCard={selectedCard}
|
||||
setSelectedCard={setSelectedCard}
|
||||
modelUrl={modelUrl}
|
||||
/>
|
||||
)}
|
||||
{/* </RenderOverlay> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardsContainer;
|
||||
96
app/src/modules/market/FilterSearch.tsx
Normal file
96
app/src/modules/market/FilterSearch.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Search from "../../components/ui/inputs/Search";
|
||||
import { StarsIcon } from "../../components/icons/marketPlaceIcons";
|
||||
import RegularDropDown from "../../components/ui/inputs/RegularDropDown";
|
||||
|
||||
interface ModelData {
|
||||
CreatedBy: string;
|
||||
animated: string | null;
|
||||
category: string;
|
||||
description: string;
|
||||
filename: string;
|
||||
isArchieve: boolean;
|
||||
modelfileID: string;
|
||||
tags: string;
|
||||
thumbnail: string;
|
||||
uploadDate: number;
|
||||
_id: string;
|
||||
price: number;
|
||||
AssetID: string;
|
||||
}
|
||||
|
||||
interface ModelsProps {
|
||||
models: ModelData[];
|
||||
setModels: React.Dispatch<React.SetStateAction<ModelData[]>>;
|
||||
filteredModels: ModelData[];
|
||||
}
|
||||
|
||||
const FilterSearch: React.FC<ModelsProps> = ({
|
||||
models,
|
||||
setModels,
|
||||
filteredModels,
|
||||
}) => {
|
||||
const [activeOption, setActiveOption] = useState("Sort by");
|
||||
const [rating, setRating] = useState(0);
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeOption === "Alphabet ascending") {
|
||||
const ascending = [...models].sort((a, b) =>
|
||||
a.filename.localeCompare(b.filename)
|
||||
);
|
||||
setModels(ascending);
|
||||
} else if (activeOption === "Alphabet descending") {
|
||||
const descending = [...models].sort((a, b) =>
|
||||
b.filename.localeCompare(a.filename)
|
||||
);
|
||||
setModels(descending);
|
||||
}
|
||||
}, [activeOption, models, setModels]);
|
||||
|
||||
const handleSearch = (val: string) => {
|
||||
const filteredModel = filteredModels.filter((model) =>
|
||||
model.filename.toLowerCase().includes(val.toLowerCase())
|
||||
);
|
||||
setModels(filteredModel);
|
||||
};
|
||||
|
||||
const handleStarClick = (index: number) => {
|
||||
setRating(index + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-search-container">
|
||||
<Search onChange={handleSearch} />
|
||||
<RegularDropDown
|
||||
header={activeOption}
|
||||
options={["Alphabet ascending", "Alphabet descending", ""]}
|
||||
onSelect={handleSelect}
|
||||
search={false}
|
||||
/>
|
||||
<div className="button">Free</div>
|
||||
<div className="button">Animated</div>
|
||||
|
||||
<div className="rating-container">
|
||||
<div className="label">Rating</div>
|
||||
<div className="stars">
|
||||
{[0, 1, 2, 3, 4].map((i) => (
|
||||
<button
|
||||
id={`${i + 1}-star-button`}
|
||||
key={i}
|
||||
onClick={() => handleStarClick(i)}
|
||||
className={`star-wrapper ${i < rating ? "filled" : "empty"}`}
|
||||
>
|
||||
<StarsIcon />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterSearch;
|
||||
77
app/src/modules/market/GltfLoader.tsx
Normal file
77
app/src/modules/market/GltfLoader.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { Stage, useGLTF } from "@react-three/drei";
|
||||
import { AnimationMixer, AnimationAction, Object3D } from "three";
|
||||
import * as THREE from "three";
|
||||
|
||||
interface GltfLoaderProps {
|
||||
glbdata?: boolean;
|
||||
fromServer?: string;
|
||||
setAnimations?: (animations?: string[]) => void;
|
||||
selectedAnimation?: string;
|
||||
setSelectedAnimation?: (animation: string) => void;
|
||||
assetId: string
|
||||
}
|
||||
|
||||
|
||||
const GltfLoader: React.FC<GltfLoaderProps> = ({
|
||||
glbdata,
|
||||
fromServer,
|
||||
setAnimations,
|
||||
selectedAnimation,
|
||||
assetId
|
||||
}) => {
|
||||
const { scene, animations } = useGLTF(fromServer ?? "") as {
|
||||
scene: Object3D;
|
||||
animations: THREE.AnimationClip[];
|
||||
};
|
||||
const mixer = useRef<AnimationMixer | null>(
|
||||
scene ? new AnimationMixer(scene) : null
|
||||
);
|
||||
const actions = useRef<Record<string, AnimationAction>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (animations.length > 0 && mixer.current && setAnimations) {
|
||||
const animationNames = animations.map((animation) => animation.name);
|
||||
setAnimations(animationNames);
|
||||
|
||||
animations.forEach((animation) => {
|
||||
const action = mixer.current!.clipAction(animation);
|
||||
actions.current[animation.name] = action;
|
||||
});
|
||||
} else {
|
||||
setAnimations && setAnimations([]);
|
||||
}
|
||||
}, [animations, setAnimations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (actions.current && selectedAnimation) {
|
||||
const action = actions.current[selectedAnimation];
|
||||
if (action) {
|
||||
action.reset().fadeIn(0.5).play();
|
||||
return () => {
|
||||
action.fadeOut(0.5);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [selectedAnimation]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (mixer.current) {
|
||||
mixer.current.update(delta);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Stage
|
||||
adjustCamera={false}
|
||||
intensity={0.5}
|
||||
shadows="contact"
|
||||
environment="city"
|
||||
>
|
||||
<primitive object={scene} scale={1} />
|
||||
</Stage>
|
||||
);
|
||||
};
|
||||
|
||||
export default GltfLoader;
|
||||
66
app/src/modules/market/MarketPlace.tsx
Normal file
66
app/src/modules/market/MarketPlace.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import FilterSearch from "./FilterSearch";
|
||||
import CardsContainer from "./CardsContainer";
|
||||
import { getAssetImages } from "../../services/factoryBuilder/assest/assets/getAssetImages";
|
||||
import SkeletonUI from "../../components/templates/SkeletonUI";
|
||||
interface ModelData {
|
||||
CreatedBy: string;
|
||||
animated: string | null;
|
||||
category: string;
|
||||
description: string;
|
||||
filename: string;
|
||||
isArchieve: boolean;
|
||||
modelfileID: string;
|
||||
tags: string;
|
||||
thumbnail: string;
|
||||
uploadDate: number;
|
||||
_id: string;
|
||||
price: number;
|
||||
AssetID: string;
|
||||
}
|
||||
const MarketPlace = () => {
|
||||
const [models, setModels] = useState<ModelData[]>([]);
|
||||
const [filteredModels, setFilteredModels] = useState<ModelData[]>([]);
|
||||
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state
|
||||
|
||||
useEffect(() => {
|
||||
echo.log("opend market place");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredAssets = async () => {
|
||||
setisLoading(true);
|
||||
try {
|
||||
const filt = await getAssetImages("67d934ad0f42a1fdadb19aa6");
|
||||
setModels(filt.items);
|
||||
setFilteredModels(filt.items);
|
||||
setisLoading(false);
|
||||
} catch {
|
||||
echo.error("Failed to filter asset");
|
||||
setisLoading(false);
|
||||
}
|
||||
};
|
||||
filteredAssets();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="marketplace-wrapper">
|
||||
<div className="marketplace-container">
|
||||
<div className="marketPlace">
|
||||
<FilterSearch
|
||||
models={models}
|
||||
setModels={setModels}
|
||||
filteredModels={filteredModels}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<SkeletonUI type="assetLibrary" /> // Show loading spinner while fetching
|
||||
) : (
|
||||
<CardsContainer models={models} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketPlace;
|
||||
Reference in New Issue
Block a user