Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter from relays #63

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions components/display-product-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import {
import ProductForm from "./product-form";
import ImageCarousel from "./utility-components/image-carousel";
import CompactCategories from "./utility-components/compact-categories";
import { locationAvatar } from "./utility-components/dropdowns/location-dropdown";
import { LocationAvatar } from "./utility-components/dropdowns/location-dropdown";
import { SHOPSTRBUTTONCLASSNAMES } from "./utility/STATIC-VARIABLES";
import RequestPassphraseModal from "./utility-components/request-passphrase-modal";
import ConfirmActionDropdown from "./utility-components/dropdowns/confirm-action-dropdown";
import { getLocalStorageData } from "./utility/nostr-helper-functions";
import { ProfileWithDropdown } from "./utility-components/profile/profile-dropdown";
import { ProductData } from "./utility/product-parser-functions";

interface ProductModalProps {
productData: any;
productData?: ProductData;
handleModalToggle: () => void;
showModal: boolean;
handleSendMessage: (pubkeyToOpenChatWith: string) => void;
Expand All @@ -43,16 +44,6 @@ export default function DisplayProductModal({
handleReviewAndPurchase,
handleDelete,
}: ProductModalProps) {
const {
pubkey,
createdAt,
title,
images,
categories,
location,
currency,
totalCost,
} = productData;
const { signInMethod, userPubkey } = getLocalStorageData();

const [requestPassphrase, setRequestPassphrase] = React.useState(false);
Expand All @@ -67,6 +58,19 @@ export default function DisplayProductModal({
return [dateString, timeString];
};

if (!productData) return null;

const {
pubkey,
createdAt,
title,
images,
categories,
location,
currency,
totalCost,
} = productData;

const handleShare = async () => {
// The content you want to share
const shareData = {
Expand Down Expand Up @@ -143,8 +147,19 @@ export default function DisplayProductModal({
pubkey={productData.pubkey}
dropDownKeys={["shop", "message"]}
/>
<Chip key={location} startContent={locationAvatar(location)}>
{location}
<Chip
key={location.regionCode || location.countryCode}
startContent={LocationAvatar({
name:
location.regionName ||
location.countryName ||
location.displayName,
iso3166: location.regionCode || location.countryCode,
})}
>
{location.regionName ||
location.displayName ||
location.countryName}
</Chip>
<CompactCategories categories={categories} />
<div>
Expand Down
157 changes: 60 additions & 97 deletions components/display-products.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState, useEffect, useContext } from "react";
import { useState, useEffect, useContext, memo } from "react";
import { Filter, SimplePool, nip19 } from "nostr-tools";
import { getLocalStorageData } from "./utility/nostr-helper-functions";
import { NostrEvent } from "../utils/types/types";
import { ProductContext, ProfileMapContext } from "../utils/context/context";
import { MyListingsContext, ProductContext } from "../utils/context/context";
import ProductCard from "./utility-components/product-card";
import DisplayProductModal from "./display-product-modal";
import { useRouter } from "next/router";
Expand All @@ -12,58 +12,32 @@ import { DeleteListing } from "../pages/api/nostr/crud-service";
import { Button } from "@nextui-org/react";
import { SHOPSTRBUTTONCLASSNAMES } from "./utility/STATIC-VARIABLES";
import { DateTime } from "luxon";
import { getNameToCodeMap } from "@/utils/location/location";
import { getKeywords } from "@/utils/text";

const DisplayEvents = ({
focusedPubkey,
selectedCategories,
selectedLocation,
selectedSearch,
canShowLoadMore,
context,
}: {
focusedPubkey?: string;
selectedCategories: Set<string>;
selectedLocation: string;
selectedSearch: string;
canShowLoadMore?: boolean;
context: typeof ProductContext | typeof MyListingsContext;
}) => {
const [productEvents, setProductEvents] = useState<ProductData[]>([]);
const [isProductsLoading, setIsProductLoading] = useState(true);
const productEventContext = useContext(ProductContext);
const profileMapContext = useContext(ProfileMapContext);
const [focusedProduct, setFocusedProduct] = useState(""); // product being viewed in modal
const productEventContext = useContext(context);

const [isLoading, setIsLoading] = useState(false);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [focusedProduct, setFocusedProduct] = useState<ProductData>(); // product being viewed in modal
const [showModal, setShowModal] = useState(false);
const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);

const router = useRouter();

const { userPubkey } = getLocalStorageData();

useEffect(() => {
if (!productEventContext) return;
if (!productEventContext.isLoading && productEventContext.productEvents) {
setIsProductLoading(true);
let sortedProductEvents = [
...productEventContext.productEvents.sort(
(a: NostrEvent, b: NostrEvent) => b.created_at - a.created_at,
),
]; // sorts most recently created to least recently created
let parsedProductData: ProductData[] = [];
sortedProductEvents.forEach((event) => {
let parsedData = parseTags(event);
if (parsedData) parsedProductData.push(parsedData);
});
setProductEvents(parsedProductData);
setIsProductLoading(false);
}
}, [productEventContext]);

const isThereAFilter = () => {
return (
selectedCategories.size > 0 ||
selectedLocation ||
selectedSearch.length > 0 ||
focusedPubkey
);
};
setIsLoading(productEventContext.isLoading);
}, [productEventContext.isLoading]);

const handleDelete = async (productId: string, passphrase?: string) => {
try {
Expand Down Expand Up @@ -101,44 +75,12 @@ const DisplayEvents = ({
router.push(`/listing/${productId}`);
};

const productSatisfiesCategoryFilter = (productData: ProductData) => {
if (selectedCategories.size === 0) return true;
return Array.from(selectedCategories).some((selectedCategory) => {
const re = new RegExp(selectedCategory, "gi");
return productData?.categories?.some((category) => {
const match = category.match(re);
return match && match.length > 0;
});
});
};

const productSatisfieslocationFilter = (productData: ProductData) => {
return !selectedLocation || productData.location === selectedLocation;
};

const productSatisfiesSearchFilter = (productData: ProductData) => {
if (!selectedSearch) return true; // nothing in search bar
if (!productData.title) return false; // we don't want to display it if product has no title
const re = new RegExp(selectedSearch, "gi");
const match = productData.title.match(re);
return match && match.length > 0;
};

const productSatisfiesAllFilters = (productData: ProductData) => {
return (
productSatisfiesCategoryFilter(productData) &&
productSatisfieslocationFilter(productData) &&
productSatisfiesSearchFilter(productData)
);
};

const displayProductCard = (
productData: ProductData,
index: number,
handleSendMessage: (pubkeyToOpenChatWith: string) => void,
) => {
if (focusedPubkey && productData.pubkey !== focusedPubkey) return;
if (!productSatisfiesAllFilters(productData)) return;

if (
(productData.pubkey ===
Expand All @@ -163,11 +105,11 @@ const DisplayEvents = ({
const loadMoreListings = async () => {
try {
setIsLoadingMore(true);
if (productEventContext.isLoading) return;
productEventContext.isLoading = true;
const oldestListing =
productEvents.length > 0
? productEvents[productEvents.length - 1]
productEventContext.productEvents.length > 0
? productEventContext.productEvents[
productEventContext.productEvents.length - 1
]
: null;
const oldestListingCreatedAt = oldestListing
? oldestListing.createdAt
Expand All @@ -179,51 +121,72 @@ const DisplayEvents = ({
);

const pool = new SimplePool();

const filter: Filter = {
kinds: [30402],
since,
until: oldestListingCreatedAt,
...(productEventContext.filters.searchQuery.length > 0 && {
"#s": getKeywords(productEventContext.filters.searchQuery),
}),
...(productEventContext.filters.location && {
"#g": [getNameToCodeMap(productEventContext.filters.location)],
}),
...(productEventContext.filters.categories.size > 0 && {
"#t": Array.from(productEventContext.filters.categories),
}),
};
const events = await pool.querySync(getLocalStorageData().relays, filter);
events.forEach((event) => {
if (event.id !== oldestListing?.id) {
productEventContext.addNewlyCreatedProductEvent(event);
const product = parseTags(event);
if (product) {
productEventContext.addNewlyCreatedProductEvents([product]);
}
}
});
productEventContext.isLoading = false;
setIsLoadingMore(false);
} catch (err) {
console.log(err);
productEventContext.isLoading = false;
setIsLoadingMore(false);
}
};

return (
<>
<div className="w-full md:pl-4">
{/* DISPLAYS PRODUCT LISTINGS HERE */}
<div className="grid h-[90%] max-w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] justify-items-center gap-4 overflow-x-hidden">
{productEvents.map((productData: ProductData, index) => {
return displayProductCard(productData, index, handleSendMessage);
})}
</div>
{profileMapContext.isLoading ||
productEventContext.isLoading ||
isProductsLoading ||
isLoadingMore ? (
{isLoading ? (
<div className="mt-8 flex items-center justify-center">
<ShopstrSpinner />
</div>
) : canShowLoadMore ? (
<div className="mt-8 h-20 px-4">
<Button
className={`${SHOPSTRBUTTONCLASSNAMES} w-full`}
onClick={async () => await loadMoreListings()}
>
Load More
</Button>
) : (
<div className="grid h-[90%] max-w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] justify-items-center gap-4 overflow-x-hidden">
{productEventContext.productEvents.map(
(productData: ProductData, index) => {
return displayProductCard(
productData,
index,
handleSendMessage,
);
},
)}
</div>
)}
{canShowLoadMore && !isLoading ? (
isLoadingMore ? (
<div className="mt-8 flex items-center justify-center">
<ShopstrSpinner />
</div>
) : (
<div className="mt-8 h-20 px-4">
<Button
className={`${SHOPSTRBUTTONCLASSNAMES} w-full`}
onClick={async () => await loadMoreListings()}
>
Load More
</Button>
</div>
)
) : null}
</div>
<DisplayProductModal
Expand Down
Loading
Loading