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

Add project search to project section in collective page #10646

Merged
merged 9 commits into from
Oct 1, 2024
Merged
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
127 changes: 0 additions & 127 deletions components/collective-page/sections/Projects.js

This file was deleted.

205 changes: 205 additions & 0 deletions components/collective-page/sections/Projects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import React from 'react';
import { gql, useQuery } from '@apollo/client';
import { css } from '@styled-system/css';
import { isEmpty } from 'lodash';
import { FormattedMessage, useIntl } from 'react-intl';
import styled from 'styled-components';

import { API_V2_CONTEXT } from '../../../lib/graphql/helpers';
import type {
ProjectsSectionSearchQuery,
ProjectsSectionSearchQueryVariables,
} from '../../../lib/graphql/types/v2/graphql';
import useDebounced from '../../../lib/hooks/useDebounced';

import { CONTRIBUTE_CARD_WIDTH } from '../../contribute-cards/constants';
import ContributeProject from '../../contribute-cards/ContributeProject';
import CreateNew from '../../contribute-cards/CreateNew';
import { EmptyResults } from '../../dashboard/EmptyResults';
import { Box } from '../../Grid';
import HorizontalScroller from '../../HorizontalScroller';
import Link from '../../Link';
import LoadingGrid from '../../LoadingGrid';
import StyledButton from '../../StyledButton';
import { P } from '../../Text';
import { Input } from '../../ui/Input';
import ContainerSectionContent from '../ContainerSectionContent';
import ContributeCardsContainer from '../ContributeCardsContainer';
import SectionTitle from '../SectionTitle';

const ProjectSectionCardFields = gql`
fragment ProjectSectionCardFields on Account {
id
legacyId
slug
name
description
imageUrl
isActive
isArchived
backgroundImageUrl(height: 208)
}
`;

const CONTRIBUTE_CARD_PADDING_X = [15, 18];

const ContributeCardContainer = styled(Box).attrs({ px: CONTRIBUTE_CARD_PADDING_X })(
css({
scrollSnapAlign: ['center', null, 'start'],
}),
);

type ProjectsProps = {
collective: {
slug: string;
name: string;
currency: string;
isActive: string;
};
projects: { id: number; isArchived?: boolean; isActive?: boolean; contributors?: object[] }[];
isAdmin: boolean;
showTitle: boolean;
};

function getContributeCardsScrollDistance(width) {
const oneCardScrollDistance = CONTRIBUTE_CARD_WIDTH + CONTRIBUTE_CARD_PADDING_X[0] * 2;
if (width <= oneCardScrollDistance * 2) {
return oneCardScrollDistance;
} else if (width <= oneCardScrollDistance * 4) {
return oneCardScrollDistance * 2;
} else {
return oneCardScrollDistance * 3;
}
}

export default function Projects(props: ProjectsProps) {
const { collective, isAdmin } = props;
const intl = useIntl();
const hasProjectsSection = (props.projects.length >= 0 && !collective.isActive) || isAdmin;

const [searchTerm, setSearchTerm] = React.useState('');
const deboucedSearchTerm = useDebounced(searchTerm, 1000);
const isSearching = !isEmpty(deboucedSearchTerm);
const query = useQuery<ProjectsSectionSearchQuery, ProjectsSectionSearchQueryVariables>(
gql`
query ProjectsSectionSearch($slug: String, $searchTerm: String) {
account(slug: $slug) {
projects: childrenAccounts(accountType: [PROJECT], searchTerm: $searchTerm) {
totalCount
nodes {
...ProjectSectionCardFields
}
}
}
}

${ProjectSectionCardFields}
`,
{
context: API_V2_CONTEXT,
variables: {
slug: props.collective.slug,
searchTerm: deboucedSearchTerm,
},
skip: !isSearching,
},
);

const searchProjects = React.useMemo(
() => (query.data?.account?.projects?.nodes || []).filter(p => isAdmin || !p.isArchived),
[isAdmin, query.data?.account?.projects?.nodes],
);
const collectiveProjects = React.useMemo(
() => props.projects.filter(p => isAdmin || !p.isArchived),
[isAdmin, props.projects],
);

const hasMore = isSearching && !query.loading && query.data?.account?.projects?.totalCount > searchProjects.length;
const isLoadingSearch = isSearching && query.loading;
const displayedProjects = !isSearching ? collectiveProjects : searchProjects;
if (!hasProjectsSection) {
return null;
}

return (
<Box pt={[4, 5]} data-cy="Projects">
<ContainerSectionContent>
<SectionTitle>
<FormattedMessage id="Projects" defaultMessage="Projects" />
</SectionTitle>
<P color="black.700" mb={4}>
{isAdmin ? (
<FormattedMessage
id="CollectivePage.SectionProjects.AdminDescription"
defaultMessage="Manage finances for a project or initiative separate from your collective budget."
/>
) : (
<FormattedMessage
id="CollectivePage.SectionProjects.Description"
defaultMessage="Support the following initiatives from {collectiveName}."
values={{ collectiveName: collective.name }}
/>
)}
</P>
{collectiveProjects?.length > 10 && (
<Input
placeholder={intl.formatMessage({ defaultMessage: 'Search projects...', id: 'Dw9Bae' })}
type="search"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
)}
</ContainerSectionContent>

<Box mb={4}>
<HorizontalScroller container={ContributeCardsContainer} getScrollDistance={getContributeCardsScrollDistance}>
{isLoadingSearch && (
<div className="ml-8 self-center">
<LoadingGrid />
</div>
)}
{isSearching && !isLoadingSearch && isEmpty(displayedProjects) && (
<div className="ml-8 self-center">
<div className="w-60 text-center">
<EmptyResults onResetFilters={() => setSearchTerm('')} hasFilters={false} entityType="PROJECTS" />
</div>
</div>
)}
{displayedProjects.map(project => (
<Box key={project.id} px={CONTRIBUTE_CARD_PADDING_X}>
<ContributeProject
collective={collective}
project={project}
disableCTA={!project.isActive}
hideContributors={!displayedProjects.some(project => project.contributors?.length)}
/>
</Box>
))}
{hasMore && (
<div className="self-center">
<div className="w-60 text-center">
<Link href={`/${collective.slug}/projects`}>
<FormattedMessage defaultMessage="More results" id="irPBg/" /> →
</Link>
</div>
</div>
)}
{isAdmin && (
<ContributeCardContainer minHeight={150}>
<CreateNew route={`/${collective.slug}/projects/create`}>
<FormattedMessage id="SectionProjects.CreateProject" defaultMessage="Create Project" />
</CreateNew>
</ContributeCardContainer>
)}
</HorizontalScroller>
<ContainerSectionContent>
<Link href={`/${collective.slug}/projects`}>
<StyledButton mt={4} width={1} buttonSize="small" fontSize="14px">
<FormattedMessage id="CollectivePage.SectionProjects.ViewAll" defaultMessage="View all projects" /> →
</StyledButton>
</Link>
</ContainerSectionContent>
</Box>
</Box>
);
}
2 changes: 1 addition & 1 deletion components/contribute-cards/ContributeProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ContributeProject = ({ collective, project, ...props }) => {
route={`${getCollectivePageRoute(collective)}/projects/${project.slug}`}
type={project.isArchived ? ContributionTypes.ARCHIVED_PROJECT : ContributionTypes.PROJECT}
contributors={project.contributors}
stats={project.stats.backers}
stats={project.stats?.backers}
image={project.backgroundImageUrl}
title={
<StyledLink as={Link} color="black.800" href={`/${collective.slug}/projects/${project.slug}`}>
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/EmptyResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function EmptyResults({
| 'VIRTUAL_CARD_REQUESTS'
| 'TAX_FORM'
| 'UPDATES'
| 'PROJECTS'
| 'TRANSACTIONS';
}) {
return (
Expand Down
2 changes: 2 additions & 0 deletions lang/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@
"du1laW": "Never",
"DUPkdl": "Tax form received for <Account></Account>",
"DVdz90": "Error updating custom email message: {error}",
"Dw9Bae": "Search projects...",
"dwABvu": "Sample payload:",
"dxKB8J": "An 8 character alpha-numeric unique transaction identifier.",
"dye8kC": "<Individual></Individual> rejected <Expense>{expenseDescription}</Expense>",
Expand Down Expand Up @@ -2182,6 +2183,7 @@
"iPy92R": "Go to {accountName}'s page",
"iqrlEx": "Hide column",
"irFBKn": "Last 7 days",
"irPBg/": "More results",
"Isedjj": "PREVIEW",
"isPw2F": "Back to updates",
"It1slB": "Virtual card suspended",
Expand Down
2 changes: 2 additions & 0 deletions lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@
"du1laW": "Never",
"DUPkdl": "Tax form received for <Account></Account>",
"DVdz90": "Error updating custom email message: {error}",
"Dw9Bae": "Search projects...",
"dwABvu": "Sample payload:",
"dxKB8J": "An 8 character alpha-numeric unique transaction identifier.",
"dye8kC": "<Individual></Individual> rejected <Expense>{expenseDescription}</Expense>",
Expand Down Expand Up @@ -2182,6 +2183,7 @@
"iPy92R": "Go to {accountName}'s page",
"iqrlEx": "Hide column",
"irFBKn": "Last 7 days",
"irPBg/": "More results",
"Isedjj": "PREVIEW",
"isPw2F": "Back to updates",
"It1slB": "Virtual card suspended",
Expand Down
2 changes: 2 additions & 0 deletions lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@
"du1laW": "Nie",
"DUPkdl": "Tax form received for <Account></Account>",
"DVdz90": "Fehler beim Aktualisieren der benutzerdefinierten E-Mail-Nachricht: {error}",
"Dw9Bae": "Search projects...",
"dwABvu": "Beispiel-Payload:",
"dxKB8J": "An 8 character alpha-numeric unique transaction identifier.",
"dye8kC": "<Individual></Individual> hat <Expense>{expenseDescription}</Expense> abgelehnt",
Expand Down Expand Up @@ -2182,6 +2183,7 @@
"iPy92R": "Gehe zu {accountName}'s Seite",
"iqrlEx": "Hide column",
"irFBKn": "Last 7 days",
"irPBg/": "More results",
"Isedjj": "PREVIEW",
"isPw2F": "Back to updates",
"It1slB": "Virtuelle Karte gesperrt",
Expand Down
Loading
Loading