From d44512be08147ffdde059922a9504d6f3d15167d Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 24 Oct 2023 11:38:10 +0200 Subject: [PATCH] feat: add pinnedIds to useProjectsWithOrders --- web-marketplace/sanity-graphql.schema.json | 216 +++++++++++++++++- .../src/generated/sanity-graphql.tsx | 46 +++- .../src/graphql/sanity/AllHomePage.graphql | 7 +- .../hooks/projects/useProjectsWithOrders.ts | 15 +- .../src/pages/Home/Home.FeaturedProjects.tsx | 23 +- web-marketplace/src/pages/Home/Home.tsx | 5 +- .../pages/Home/hooks/useFeaturedProjects.ts | 8 +- .../src/pages/Projects/utils/sortProjects.ts | 23 ++ 8 files changed, 317 insertions(+), 26 deletions(-) diff --git a/web-marketplace/sanity-graphql.schema.json b/web-marketplace/sanity-graphql.schema.json index 9adec82cdc..1156ae7130 100644 --- a/web-marketplace/sanity-graphql.schema.json +++ b/web-marketplace/sanity-graphql.schema.json @@ -26189,7 +26189,7 @@ "args": [], "type": { "kind": "OBJECT", - "name": "TitleCustomBody", + "name": "HomePageProjectsSection", "ofType": null }, "isDeprecated": false, @@ -26362,7 +26362,7 @@ "description": null, "type": { "kind": "INPUT_OBJECT", - "name": "TitleCustomBodyFilter", + "name": "HomePageProjectsSectionFilter", "ofType": null }, "defaultValue": null, @@ -26410,6 +26410,163 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "HomePageProjectsSection", + "description": null, + "fields": [ + { + "name": "_key", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_type", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "titleCustomBody", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "TitleCustomBody", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "projects", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "HomePageProjectsSectionFilter", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "_key", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "StringFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_type", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "StringFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "titleCustomBody", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "TitleCustomBodyFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "HomePageProjectsSectionSorting", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "_key", + "description": null, + "type": { + "kind": "ENUM", + "name": "SortOrder", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_type", + "description": null, + "type": { + "kind": "ENUM", + "name": "SortOrder", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "titleCustomBody", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "TitleCustomBodySorting", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "HomePageSorting", @@ -26517,7 +26674,7 @@ "description": null, "type": { "kind": "INPUT_OBJECT", - "name": "TitleCustomBodySorting", + "name": "HomePageProjectsSectionSorting", "ofType": null }, "defaultValue": null, @@ -40942,7 +41099,7 @@ }, { "name": "projectId", - "description": "on-chain project id", + "description": "on-chain project id, off-chain uuid or slug", "args": [], "type": { "kind": "SCALAR", @@ -66655,6 +66812,57 @@ } ], "directives": [ + { + "name": "crossDatasetReference", + "description": "Field references one or more document in another dataset", + "isRepeatable": false, + "locations": [ + "OBJECT", + "FIELD_DEFINITION" + ], + "args": [ + { + "name": "dataset", + "description": "Dataset name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "typeNames", + "description": "Strings of the target types names enabled for this reference", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "name": "jsonAlias", "description": "Field is a \"raw\" JSON alias for a different field", diff --git a/web-marketplace/src/generated/sanity-graphql.tsx b/web-marketplace/src/generated/sanity-graphql.tsx index a012c5e764..e7d409fc04 100644 --- a/web-marketplace/src/generated/sanity-graphql.tsx +++ b/web-marketplace/src/generated/sanity-graphql.tsx @@ -22,6 +22,7 @@ export type Scalars = { + export type ActionCard = { __typename?: 'ActionCard'; _key?: Maybe; @@ -3011,7 +3012,7 @@ export type HomePage = Document & { _key?: Maybe; seo?: Maybe; heroSection?: Maybe; - projectsSection?: Maybe; + projectsSection?: Maybe; creditClassesSection?: Maybe; gettingStartedResourcesSection?: Maybe; bottomBanner?: Maybe; @@ -3028,12 +3029,32 @@ export type HomePageFilter = { _key?: Maybe; seo?: Maybe; heroSection?: Maybe; - projectsSection?: Maybe; + projectsSection?: Maybe; creditClassesSection?: Maybe; gettingStartedResourcesSection?: Maybe; bottomBanner?: Maybe; }; +export type HomePageProjectsSection = { + __typename?: 'HomePageProjectsSection'; + _key?: Maybe; + _type?: Maybe; + titleCustomBody?: Maybe; + projects?: Maybe>>; +}; + +export type HomePageProjectsSectionFilter = { + _key?: Maybe; + _type?: Maybe; + titleCustomBody?: Maybe; +}; + +export type HomePageProjectsSectionSorting = { + _key?: Maybe; + _type?: Maybe; + titleCustomBody?: Maybe; +}; + export type HomePageSorting = { _id?: Maybe; _type?: Maybe; @@ -3043,7 +3064,7 @@ export type HomePageSorting = { _key?: Maybe; seo?: Maybe; heroSection?: Maybe; - projectsSection?: Maybe; + projectsSection?: Maybe; creditClassesSection?: Maybe; bottomBanner?: Maybe; }; @@ -4707,7 +4728,7 @@ export type Project = Document & { _rev?: Maybe; _key?: Maybe; projectName?: Maybe; - /** on-chain project id */ + /** on-chain project id, off-chain uuid or slug */ projectId?: Maybe; image?: Maybe; location?: Maybe; @@ -8084,8 +8105,14 @@ export type AllHomePageQuery = ( { __typename?: 'GettingStartedResourcesSection' } & GettingStartedResourcesSectionFieldsFragment )>, projectsSection?: Maybe<( - { __typename?: 'TitleCustomBody' } - & TitleCustomBodyFieldsFragment + { __typename?: 'HomePageProjectsSection' } + & { titleCustomBody?: Maybe<( + { __typename?: 'TitleCustomBody' } + & TitleCustomBodyFieldsFragment + )>, projects?: Maybe + )>>> } )>, creditClassesSection?: Maybe<( { __typename?: 'TitleCustomBody' } & TitleCustomBodyFieldsFragment @@ -9956,7 +9983,12 @@ export const AllHomePageDocument = gql` ...gettingStartedResourcesSectionFields } projectsSection { - ...titleCustomBodyFields + titleCustomBody { + ...titleCustomBodyFields + } + projects { + projectId + } } creditClassesSection { ...titleCustomBodyFields diff --git a/web-marketplace/src/graphql/sanity/AllHomePage.graphql b/web-marketplace/src/graphql/sanity/AllHomePage.graphql index 6113d5f22e..1978f07a80 100644 --- a/web-marketplace/src/graphql/sanity/AllHomePage.graphql +++ b/web-marketplace/src/graphql/sanity/AllHomePage.graphql @@ -20,7 +20,12 @@ query allHomePage { ...gettingStartedResourcesSectionFields } projectsSection { - ...titleCustomBodyFields + titleCustomBody { + ...titleCustomBodyFields + } + projects { + projectId + } } creditClassesSection { ...titleCustomBodyFields diff --git a/web-marketplace/src/hooks/projects/useProjectsWithOrders.ts b/web-marketplace/src/hooks/projects/useProjectsWithOrders.ts index 1be9ccbfcb..6a3ce0c636 100644 --- a/web-marketplace/src/hooks/projects/useProjectsWithOrders.ts +++ b/web-marketplace/src/hooks/projects/useProjectsWithOrders.ts @@ -19,7 +19,10 @@ import { useWallet } from 'lib/wallet/wallet'; import { useFetchAllOffChainProjects } from 'pages/Projects/hooks/useOffChainProjects'; import { ProjectsSellOrders } from 'pages/Projects/hooks/useProjectsSellOrders.types'; -import { sortProjects } from 'pages/Projects/utils/sortProjects'; +import { + sortPinnedProject, + sortProjects, +} from 'pages/Projects/utils/sortProjects'; import { useClassesWithMetadata } from 'hooks/classes/useClassesWithMetadata'; import { useLastRandomProjects } from './useLastRandomProjects'; @@ -35,6 +38,7 @@ export interface ProjectsWithOrdersProps { projectId?: string; // to filter by project skippedProjectId?: string; // to discard a specific project classId?: string; // to filter by class + pinnedIds?: string[]; // list of on-chain id, uuid or slug to pinned at the top sort?: string; creditClassFilter?: Record; } @@ -51,6 +55,7 @@ export function useProjectsWithOrders({ useOffChainProjects = false, skippedProjectId, classId, + pinnedIds, sort = '', projectId, creditClassFilter = {}, @@ -164,16 +169,16 @@ export function useProjectsWithOrders({ const creditClassSelected = Object.keys(creditClassFilter).filter( creditClassId => creditClassFilter[creditClassId], ); + const projectsFilteredByCreditClass = allProject.filter(project => creditClassSelected.length === 0 ? true : creditClassSelected.includes(project.creditClassId ?? ''), ); - const sortedProjects = sortProjects( - projectsFilteredByCreditClass, - sort, - ).slice(offset, limit ? offset + limit : undefined); + const sortedProjects = sortProjects(projectsFilteredByCreditClass, sort) + .sort((a, b) => sortPinnedProject(a, b, pinnedIds)) + .slice(offset, limit ? offset + limit : undefined); /* Metadata queries */ diff --git a/web-marketplace/src/pages/Home/Home.FeaturedProjects.tsx b/web-marketplace/src/pages/Home/Home.FeaturedProjects.tsx index 209503a037..c547f9c31f 100644 --- a/web-marketplace/src/pages/Home/Home.FeaturedProjects.tsx +++ b/web-marketplace/src/pages/Home/Home.FeaturedProjects.tsx @@ -4,7 +4,11 @@ import { Box } from '@mui/material'; import ContainedButton from 'web-components/lib/components/buttons/ContainedButton'; -import { Maybe, Scalars } from 'generated/sanity-graphql'; +import { + HomePageProjectsSection, + Maybe, + Scalars, +} from 'generated/sanity-graphql'; import { BuySellOrderFlow } from 'features/marketplace/BuySellOrderFlow/BuySellOrderFlow'; import { ProjectWithOrderData } from 'pages/Projects/Projects.types'; @@ -12,14 +16,21 @@ import { ProjectCardsSection } from 'components/organisms/ProjectCardsSection/Pr import { useFeaturedProjects } from './hooks/useFeaturedProjects'; +type Props = { + title: string; + body: Maybe; + sanityFeaturedProjects: HomePageProjectsSection['projects']; +}; + export function FeaturedProjects({ title, body, -}: { - title: string; - body: Maybe; -}): JSX.Element { - const { featuredProjects, loading } = useFeaturedProjects(); + sanityFeaturedProjects, +}: Props): JSX.Element { + const pinnedIds = sanityFeaturedProjects?.map(project => + String(project?.projectId), + ); + const { featuredProjects, loading } = useFeaturedProjects({ pinnedIds }); const [selectedProject, setSelectedProject] = useState(null); const [isBuyFlowStarted, setIsBuyFlowStarted] = useState(false); diff --git a/web-marketplace/src/pages/Home/Home.tsx b/web-marketplace/src/pages/Home/Home.tsx index 0d2bfc1d3e..08bd663ee5 100644 --- a/web-marketplace/src/pages/Home/Home.tsx +++ b/web-marketplace/src/pages/Home/Home.tsx @@ -153,8 +153,9 @@ const Home: React.FC> = () => { {creditClasses && ( diff --git a/web-marketplace/src/pages/Home/hooks/useFeaturedProjects.ts b/web-marketplace/src/pages/Home/hooks/useFeaturedProjects.ts index d1232da20b..053716e210 100644 --- a/web-marketplace/src/pages/Home/hooks/useFeaturedProjects.ts +++ b/web-marketplace/src/pages/Home/hooks/useFeaturedProjects.ts @@ -3,7 +3,11 @@ import { useProjectsWithOrders } from 'hooks/projects/useProjectsWithOrders'; import { FEATURE_PROJECTS_COUNT, PROJECTS_SORT } from '../Home.constants'; -export function useFeaturedProjects(): { +type Props = { + pinnedIds?: string[]; +}; + +export function useFeaturedProjects({ pinnedIds }: Props): { featuredProjects: ProjectWithOrderData[]; loading: boolean; } { @@ -12,6 +16,8 @@ export function useFeaturedProjects(): { limit: FEATURE_PROJECTS_COUNT, metadata: true, // to discard projects without metadata prop sort: PROJECTS_SORT, + pinnedIds, + useOffChainProjects: true, }); return { featuredProjects: projectsWithOrderData, diff --git a/web-marketplace/src/pages/Projects/utils/sortProjects.ts b/web-marketplace/src/pages/Projects/utils/sortProjects.ts index a0a51e3c68..1ee7a17f9d 100644 --- a/web-marketplace/src/pages/Projects/utils/sortProjects.ts +++ b/web-marketplace/src/pages/Projects/utils/sortProjects.ts @@ -78,6 +78,29 @@ function compareQuantityDescending( return 0; } +// Sort pinned project at the beginning +export function sortPinnedProject( + a: ProjectWithOrderData, + b: ProjectWithOrderData, + pinnedIds?: string[], +): number { + if (!pinnedIds) return 0; + + const aIdIndex = pinnedIds?.indexOf(a.id); + const aIndex = aIdIndex > 0 ? aIdIndex : pinnedIds?.indexOf(a.name); + + const bIdIndex = pinnedIds?.indexOf(b.id); + const bIndex = bIdIndex > 0 ? bIdIndex : pinnedIds?.indexOf(b.name); + + if (aIndex >= 0 && bIndex === -1) return -1; + if (aIndex === -1 && bIndex >= 0) return 1; + if (aIndex >= 0 && bIndex >= 0) { + return aIndex < bIndex ? -1 : 1; + } + + return 0; +} + function getQuantities( a: ProjectWithOrderData, b: ProjectWithOrderData,