Milestone 2: Data is showing, filters are working, search is still broken, links are clickable
parent
e5d5e6e978
commit
a1b940f932
|
|
@ -0,0 +1,17 @@
|
|||
Vendor,Vendor rating,Type,Compound,Dose,Unit,Format,Quantity,Price,Ships from Country,Shipping $,MOQ,Analysis,Purity guarantee,Mass guarantee,Re-ship guarantee,Start,Close,PepChat Link,Discord Link,Telegram Link,Notes
|
||||
SRY,A,promo,Tirzepatide,15,mg,3mL vial,10, $ 125 ,China,included,2,2nd party,>99%,overfill,yes,2024-09-14,While stocks last,https://peptide.chat/invite/srylab,https://discord.gg/yafWDPuC,,
|
||||
QSC,AA,promo,Retatrutide,10,mg,3mL vial,10, $ 180 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
QSC,AA,promo,Retatrutide,15,mg,3mL vial,10, $ 270 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
QSC,AA,promo,Retatrutide,20,mg,3mL vial,10, $ 360 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
QSC,AA,promo,Retatrutide,10,mg,3mL vial,10, $ 180 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
QSC,AA,promo,Retatrutide,15,mg,3mL vial,10, $ 270 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
QSC,AA,promo,Retatrutide,20,mg,3mL vial,10, $ 360 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||
ASC,A,promo,Tirzepatide,60,mg,3mL vial,10, $ 470 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||
ASC,A,promo,Retatrutide,12,mg,3mL vial,10, $ 240 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||
ASC,A,promo,GHK-Cu,50,mg,3mL vial,10, $ 45 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||
JL,AAA,GB,Melanotan 1,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
JL,AAA,GB,Melanotan 2,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
JL,AAA,GB,Thymulin,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
JL,AAA,GB,PT-141,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
JL,AAA,GB,Sermorelin,10,mg,3mL vial,10, $ 120 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
JL,AAA,GB,Survodutide,10,mg,3mL vial,10, $ 190 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
id,name,description,compound,dose,measurement,format,quantity,priceUSD,shipping,testing,guarantees,vendorRating
|
||||
1,Compound Vendor A,High-quality chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
||||
2,Compound Vendor B,Specialized in organic compounds,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
||||
3,Compound Vendor C,Leading supplier of rare compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.2
|
||||
4,Compound Vendor D,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
||||
5,Compound Vendor E,Innovative solutions for compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
||||
6,Compound Vendor F,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
||||
7,Compound Vendor G,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
||||
8,Compound Vendor H,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
||||
9,Compound Vendor I,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
||||
10,Compound Vendor J,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
||||
11,Compound Vendor K,Innovative compound solutions,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
||||
12,Compound Vendor L,Quality compounds for research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
||||
13,Compound Vendor M,Leading supplier of chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
||||
14,Compound Vendor N,Trusted by researchers worldwide,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
||||
15,Compound Vendor O,Innovative solutions for chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
||||
16,Compound Vendor P,Quality compounds at affordable prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
||||
17,Compound Vendor Q,Your partner in compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
||||
18,Compound Vendor R,Excellence in compound supply,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
||||
19,Compound Vendor S,Global leader in chemical distribution,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.5
|
||||
20,Compound Vendor T,Trusted by top research labs,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
||||
21,Compound Vendor U,Innovative compound solutions for research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.4
|
||||
22,Compound Vendor V,Quality compounds for scientific research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
||||
23,Compound Vendor W,Leading supplier of research compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
||||
24,Compound Vendor X,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
||||
25,Compound Vendor Y,Innovative solutions for compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
||||
26,Compound Vendor Z,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
||||
27,Compound Vendor AA,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
||||
28,Compound Vendor AB,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
||||
29,Compound Vendor AC,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
||||
30,Compound Vendor AD,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
||||
31,Compound Vendor AE,Innovative compound solutions,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
||||
32,Compound Vendor AF,Quality compounds for research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
||||
33,Compound Vendor AG,Leading supplier of chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
||||
34,Compound Vendor AH,Trusted by researchers worldwide,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
||||
35,Compound Vendor AI,Innovative solutions for chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
||||
36,Compound Vendor AJ,Quality compounds at affordable prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
||||
37,Compound Vendor AK,Your partner in compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
||||
38,Compound Vendor AL,Excellence in compound supply,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
||||
39,Compound Vendor AM,Global leader in chemical distribution,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.5
|
||||
40,Compound Vendor AN,Trusted by top research labs,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
||||
41,Compound Vendor AO,Innovative compound solutions for research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.4
|
||||
42,Compound Vendor AP,Quality compounds for scientific research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
||||
43,Compound Vendor AQ,Leading supplier of research compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
||||
44,Compound Vendor AR,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
||||
45,Compound Vendor AS,Innovative solutions for compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
||||
46,Compound Vendor AT,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
||||
47,Compound Vendor AU,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
||||
48,Compound Vendor AV,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
||||
49,Compound Vendor AW,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
||||
50,Compound Vendor AX,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* CompoundBay.module.scss */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.searchBarContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
width: 300px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Styles for FilterSidebar
|
||||
.filterSidebar {
|
||||
width: 250px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
// Styles for ResultsSidebar
|
||||
.results {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
|
|
@ -1,200 +1,227 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import Papa from "papaparse";
|
||||
import { useState, useEffect } from "react";
|
||||
import styled from "styled-components/macro";
|
||||
import { FaSearch } from "react-icons/fa";
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: #f0f0f0;
|
||||
font-family: "Roboto", sans-serif;
|
||||
`;
|
||||
import styles from "./CompoundBay.module.scss";
|
||||
|
||||
const Banner = styled.h1`
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
color: #1a237e;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const SearchBar = styled.input`
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #bdbdbd;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
font-size: 1em;
|
||||
font-family: "Roboto", sans-serif;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
`;
|
||||
|
||||
const Filters = styled.div`
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-right: 20px;
|
||||
`;
|
||||
|
||||
const Results = styled.div`
|
||||
flex: 3;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
height: 600px; /* Set a fixed height for scrolling */
|
||||
`;
|
||||
|
||||
const FilterTitle = styled.h3`
|
||||
margin-bottom: 10px;
|
||||
color: #1a237e;
|
||||
font-size: 1.2em;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const FilterOption = styled.div`
|
||||
margin-bottom: 10px;
|
||||
color: #424242;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
&:hover {
|
||||
color: #1a237e;
|
||||
}
|
||||
`;
|
||||
|
||||
const ResultCard = styled.div`
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
background-color: #ffffff;
|
||||
`;
|
||||
|
||||
const VendorName = styled.h3`
|
||||
color: #1a237e;
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const VendorDescription = styled.p`
|
||||
color: #616161;
|
||||
font-size: 1em;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const VendorDetail = styled.p`
|
||||
color: #424242;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
import { GroupBuySale } from "../../types/groupBuySale";
|
||||
import FilterSidebar from "./FilterSidebar";
|
||||
import ResultsSidebar from "./ResultsSidebar";
|
||||
|
||||
const CompoundBay = observer(() => {
|
||||
const [groupBuySales, setGroupBuySales] = useState<GroupBuySale[]>([]);
|
||||
const [visibleGroupBuySales, setVisibleGroupBuySales] = useState<
|
||||
GroupBuySale[]
|
||||
>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [vendors, setVendors] = useState([]);
|
||||
const [visibleVendors, setVisibleVendors] = useState([]);
|
||||
const [loadCount, setLoadCount] = useState(10);
|
||||
const [expandedSales, setExpandedSales] = useState<Set<string>>(new Set());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Filter states
|
||||
const [vendorRating, setVendorRating] = useState("");
|
||||
const [vendor, setVendor] = useState("");
|
||||
const [shipsFromCountry, setShipsFromCountry] = useState("");
|
||||
const [compound, setCompound] = useState("");
|
||||
|
||||
// Add new state for dropdown options
|
||||
const [vendorOptions, setVendorOptions] = useState<string[]>([]);
|
||||
const [countryOptions, setCountryOptions] = useState<string[]>([]);
|
||||
const [compoundOptions, setCompoundOptions] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Load and parse the CSV file
|
||||
Papa.parse("/src/data/vendors.csv", {
|
||||
download: true,
|
||||
header: true,
|
||||
complete: (results) => {
|
||||
setVendors(results.data);
|
||||
setVisibleVendors(results.data.slice(0, loadCount));
|
||||
},
|
||||
});
|
||||
const fetchGroupBuySales = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch("/data/group_buy_sales.csv");
|
||||
const csvText = await response.text();
|
||||
Papa.parse(csvText, {
|
||||
header: true,
|
||||
complete: (results) => {
|
||||
const parsedSales = results.data.map((row: any) => ({
|
||||
vendor: row.Vendor,
|
||||
vendorRating: row["Vendor rating"],
|
||||
type: row.Type,
|
||||
compound: row.Compound,
|
||||
dose: row.Dose,
|
||||
unit: row.Unit,
|
||||
format: row.Format,
|
||||
quantity: row.Quantity,
|
||||
price: row.Price,
|
||||
shipsFromCountry: row["Ships from Country"],
|
||||
shippingCost: row["Shipping $"],
|
||||
moq: row.MOQ,
|
||||
analysis: row.Analysis,
|
||||
purityGuarantee: row["Purity guarantee"],
|
||||
massGuarantee: row["Mass guarantee"],
|
||||
reshipGuarantee: row["Re-ship guarantee"],
|
||||
start: row.Start,
|
||||
close: row.Close,
|
||||
pepChatLink: row["PepChat Link"],
|
||||
discordLink: row["Discord Link"],
|
||||
telegramLink: row["Telegram Link"],
|
||||
notes: row.Notes,
|
||||
})) as GroupBuySale[];
|
||||
|
||||
console.log("Parsed sales:", parsedSales); // Debug log
|
||||
|
||||
// Check for missing properties
|
||||
const validSales = parsedSales.filter((sale) => {
|
||||
if (
|
||||
!sale.vendor ||
|
||||
!sale.compound ||
|
||||
!sale.shipsFromCountry
|
||||
) {
|
||||
console.warn("Invalid sale object:", sale);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setGroupBuySales(validSales);
|
||||
setVisibleGroupBuySales(validSales.slice(0, loadCount));
|
||||
|
||||
// Extract unique options for dropdowns
|
||||
const vendors = [
|
||||
...new Set(validSales.map((sale) => sale.vendor)),
|
||||
];
|
||||
const countries = [
|
||||
...new Set(
|
||||
validSales.map((sale) => sale.shipsFromCountry),
|
||||
),
|
||||
];
|
||||
const compounds = [
|
||||
...new Set(validSales.map((sale) => sale.compound)),
|
||||
];
|
||||
|
||||
setVendorOptions(vendors);
|
||||
setCountryOptions(countries);
|
||||
setCompoundOptions(compounds);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error("Error parsing CSV:", err);
|
||||
setError("Failed to load group buy sales data.");
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error fetching CSV:", err);
|
||||
setError("Failed to fetch group buy sales data.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchGroupBuySales();
|
||||
}, []);
|
||||
|
||||
const loadMoreVendors = () => {
|
||||
setLoadCount((prevCount) => prevCount + 10);
|
||||
setVisibleVendors(vendors.slice(0, loadCount + 10));
|
||||
useEffect(() => {
|
||||
const filteredSales = groupBuySales.filter((sale) => {
|
||||
return (
|
||||
(vendorRating === "" || sale.vendorRating === vendorRating) &&
|
||||
(vendor === "" ||
|
||||
sale.vendor.toLowerCase().includes(vendor.toLowerCase())) &&
|
||||
(shipsFromCountry === "" ||
|
||||
sale.shipsFromCountry
|
||||
.toLowerCase()
|
||||
.includes(shipsFromCountry.toLowerCase())) &&
|
||||
(compound === "" ||
|
||||
sale.compound
|
||||
.toLowerCase()
|
||||
.includes(compound.toLowerCase())) &&
|
||||
(searchTerm === "" ||
|
||||
sale.vendor
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
sale.compound
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()))
|
||||
);
|
||||
});
|
||||
|
||||
console.log("Filtered sales:", filteredSales); // Debug log
|
||||
setVisibleGroupBuySales(filteredSales.slice(0, loadCount));
|
||||
}, [
|
||||
groupBuySales,
|
||||
vendorRating,
|
||||
vendor,
|
||||
shipsFromCountry,
|
||||
compound,
|
||||
searchTerm,
|
||||
loadCount,
|
||||
]);
|
||||
|
||||
const handleSearch = () => {
|
||||
setLoadCount(10);
|
||||
};
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const bottom =
|
||||
e.target.scrollHeight - e.target.scrollTop ===
|
||||
e.target.clientHeight;
|
||||
if (bottom) {
|
||||
loadMoreVendors();
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||
if (scrollHeight - scrollTop <= clientHeight * 1.5) {
|
||||
setLoadCount((prevCount) => prevCount + 10);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpand = (id: string) => {
|
||||
setExpandedSales((prevExpanded) => {
|
||||
const newExpanded = new Set(prevExpanded);
|
||||
if (newExpanded.has(id)) {
|
||||
newExpanded.delete(id);
|
||||
} else {
|
||||
newExpanded.add(id);
|
||||
}
|
||||
return newExpanded;
|
||||
});
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
setVendorRating("");
|
||||
setVendor("");
|
||||
setShipsFromCountry("");
|
||||
setCompound("");
|
||||
setSearchTerm("");
|
||||
setLoadCount(10);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Banner>CompoundBay</Banner>
|
||||
<SearchBar
|
||||
type="text"
|
||||
placeholder="Search vendors..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<Content>
|
||||
<Filters>
|
||||
<FilterTitle>Sort By</FilterTitle>
|
||||
<FilterOption>Relevance</FilterOption>
|
||||
<FilterOption>Rating</FilterOption>
|
||||
<FilterTitle>Categories</FilterTitle>
|
||||
<FilterOption>Organic</FilterOption>
|
||||
<FilterOption>Inorganic</FilterOption>
|
||||
<FilterTitle>Available On</FilterTitle>
|
||||
<FilterOption>Online</FilterOption>
|
||||
<FilterOption>In-store</FilterOption>
|
||||
</Filters>
|
||||
<Results onScroll={handleScroll}>
|
||||
{visibleVendors.map((vendor) => (
|
||||
<ResultCard key={vendor.id}>
|
||||
<VendorName>{vendor.name}</VendorName>
|
||||
<VendorDescription>
|
||||
{vendor.description}
|
||||
</VendorDescription>
|
||||
<VendorDetail>
|
||||
<strong>Compound:</strong> {vendor.compound}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Dose:</strong> {vendor.dose}{" "}
|
||||
{vendor.measurement}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Format:</strong> {vendor.format}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Quantity:</strong> {vendor.quantity}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Price USD:</strong> ${vendor.priceUSD}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Shipping:</strong> {vendor.shipping}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Testing:</strong> {vendor.testing}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Guarantees:</strong> {vendor.guarantees}
|
||||
</VendorDetail>
|
||||
<VendorDetail>
|
||||
<strong>Vendor Rating:</strong>{" "}
|
||||
{vendor.vendorRating} / 5
|
||||
</VendorDetail>
|
||||
</ResultCard>
|
||||
))}
|
||||
</Results>
|
||||
</Content>
|
||||
</Container>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.banner}>CompoundBay</h1>
|
||||
<div className={styles.searchBarContainer}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search compounds..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className={styles.searchBar}
|
||||
/>
|
||||
<button className={styles.searchButton} onClick={handleSearch}>
|
||||
<FaSearch />
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<FilterSidebar
|
||||
vendorRating={vendorRating}
|
||||
vendor={vendor}
|
||||
shipsFromCountry={shipsFromCountry}
|
||||
compound={compound}
|
||||
handleVendorRatingChange={setVendorRating}
|
||||
handleVendorChange={setVendor}
|
||||
handleShipsFromCountryChange={setShipsFromCountry}
|
||||
handleCompoundChange={setCompound}
|
||||
clearFilters={clearFilters}
|
||||
vendorOptions={vendorOptions}
|
||||
countryOptions={countryOptions}
|
||||
compoundOptions={compoundOptions}
|
||||
/>
|
||||
<ResultsSidebar
|
||||
visibleGroupBuySales={visibleGroupBuySales}
|
||||
expandedSales={expandedSales}
|
||||
toggleExpand={toggleExpand}
|
||||
handleScroll={handleScroll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
.filters {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.filterSidebar {
|
||||
width: 250px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #ddd;
|
||||
overflow-y: auto;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filterSection {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.clearFilters {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import React from "react";
|
||||
|
||||
import styles from "./FilterSidebar.module.scss";
|
||||
|
||||
interface FilterSidebarProps {
|
||||
vendorRating: string;
|
||||
vendor: string;
|
||||
shipsFromCountry: string;
|
||||
compound: string;
|
||||
handleVendorRatingChange: (rating: string) => void;
|
||||
handleVendorChange: (vendor: string) => void;
|
||||
handleShipsFromCountryChange: (country: string) => void;
|
||||
handleCompoundChange: (compound: string) => void;
|
||||
clearFilters: () => void;
|
||||
vendorOptions: string[];
|
||||
countryOptions: string[];
|
||||
compoundOptions: string[];
|
||||
}
|
||||
|
||||
const FilterSidebar: React.FC<FilterSidebarProps> = ({
|
||||
vendorRating,
|
||||
vendor,
|
||||
shipsFromCountry,
|
||||
compound,
|
||||
handleVendorRatingChange,
|
||||
handleVendorChange,
|
||||
handleShipsFromCountryChange,
|
||||
handleCompoundChange,
|
||||
clearFilters,
|
||||
vendorOptions,
|
||||
countryOptions,
|
||||
compoundOptions,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.filterSidebar}>
|
||||
<h2>Filters</h2>
|
||||
|
||||
<div className={styles.filterSection}>
|
||||
<h3>Vendor Rating</h3>
|
||||
<select
|
||||
value={vendorRating}
|
||||
onChange={(e) => handleVendorRatingChange(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
<option value="A">A</option>
|
||||
<option value="AA">AA</option>
|
||||
<option value="AAA">AAA</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterSection}>
|
||||
<h3>Vendor</h3>
|
||||
<select
|
||||
value={vendor}
|
||||
onChange={(e) => handleVendorChange(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{vendorOptions.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterSection}>
|
||||
<h3>Ships from Country</h3>
|
||||
<select
|
||||
value={shipsFromCountry}
|
||||
onChange={(e) =>
|
||||
handleShipsFromCountryChange(e.target.value)
|
||||
}>
|
||||
<option value="">All</option>
|
||||
{countryOptions.map((c) => (
|
||||
<option key={c} value={c}>
|
||||
{c}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterSection}>
|
||||
<h3>Compound</h3>
|
||||
<select
|
||||
value={compound}
|
||||
onChange={(e) => handleCompoundChange(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{compoundOptions.map((c) => (
|
||||
<option key={c} value={c}>
|
||||
{c}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onClick={clearFilters} className={styles.clearFilters}>
|
||||
Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterSidebar;
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
.results {
|
||||
flex-grow: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.saleItem {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.saleTitle {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.salePrice {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.expandedDetails {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.detailsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detailItem {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detailLabel {
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.notes {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.linkItem {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.noResults {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue