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

Feat/delete draft projec #4793

Merged
merged 17 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"devDependencies": {
"@babel/preset-typescript": "^7.23.3",
"@next/bundle-analyzer": "^14.1.0",
"@tanstack/react-query-devtools": "^5.58.0",
"@testing-library/cypress": "^10.0.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
Expand Down
50 changes: 37 additions & 13 deletions src/apollo/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useMemo } from 'react';
import { ApolloClient, InMemoryCache, ApolloLink } from '@apollo/client';
import {
ApolloClient,
InMemoryCache,
ApolloLink,
NormalizedCacheObject,
} from '@apollo/client';
import { RetryLink } from '@apollo/client/link/retry';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
Expand All @@ -14,20 +19,21 @@ import { signOut } from '@/features/user/user.thunks';
import config from '@/configuration';
import { setShowSignWithWallet } from '@/features/modal/modal.slice';

let apolloClient: any;
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;

const ssrMode = isSSRMode;

export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';

const parseHeaders = (rawHeaders: any) => {
// Parses headers into the Headers object
const parseHeaders = (rawHeaders: string) => {
const headers = new Headers();
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
preProcessedHeaders.split(/\r?\n/).forEach((line: any) => {
preProcessedHeaders.split(/\r?\n/).forEach((line: string) => {
const parts = line.split(':');
const key = parts.shift().trim();
const key = parts.shift()?.trim();
if (key) {
const value = parts.join(':').trim();
headers.append(key, value);
Expand All @@ -36,6 +42,7 @@ const parseHeaders = (rawHeaders: any) => {
return headers;
};

// Custom fetch logic with file upload handling
const uploadFetch = (url: string, options: any) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
Expand All @@ -49,8 +56,11 @@ const uploadFetch = (url: string, options: any) =>
'responseURL' in xhr
? xhr.responseURL
: opts.headers.get('X-Request-URL');
// TypeScript fix: Explicitly cast `xhr` to `XMLHttpRequest` to access responseText
const body =
'response' in xhr ? xhr.response : (xhr as any).responseText;
'response' in xhr
? xhr.response
: (xhr as XMLHttpRequest).responseText;
resolve(new Response(body, opts));
};
xhr.onerror = () => {
Expand All @@ -76,26 +86,30 @@ const uploadFetch = (url: string, options: any) =>
xhr.send(options.body);
});

const customFetch = (uri: any, options: any) => {
// Custom fetch function to determine when to use upload fetch or standard fetch
const customFetch = (uri: string, options: any) => {
MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved
if (options.useUpload) {
return uploadFetch(uri, options);
}
return fetch(uri, options);
};

function createApolloClient() {
// Creates the Apollo Client with the custom link setup
function createApolloClient(): ApolloClient<NormalizedCacheObject> {
let userWalletAddress: string | null;
if (!ssrMode) {
userWalletAddress = localStorage.getItem(StorageLabel.USER);
}

const retryLink = new RetryLink();

// Custom link for handling file uploads
const httpLink = createUploadLink({
uri: config.BACKEND_LINK,
fetch: customFetch as any,
MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved
});

// Auth link to add Authorization and locale headers
const authLink = setContext((_, { headers }) => {
let locale: string | null = !ssrMode
? localStorage.getItem(StorageLabel.LOCALE)
Expand All @@ -117,12 +131,13 @@ function createApolloClient() {
};
});

// Error handling link
const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
console.log('operation', operation);
graphQLErrors.forEach(err => {
console.error('err', JSON.stringify(err));
const { message, locations, path } = err;
const { message } = err;
if (message.toLowerCase().includes('authentication required')) {
console.log(Date.now(), 'sign out from graphQL');
// removes token and user from store
Expand Down Expand Up @@ -190,7 +205,10 @@ function createApolloClient() {
});
}

export function initializeApollo(initialState = null) {
// Initialize Apollo Client for SSR and client-side rendering
export function initializeApollo(
initialState: any = null,
): ApolloClient<NormalizedCacheObject> {
const _apolloClient = apolloClient ?? createApolloClient();

// If your page has Next.js data fetching methods that use Apollo Client, the initial state
Expand All @@ -202,7 +220,7 @@ export function initializeApollo(initialState = null) {
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
arrayMerge: (destinationArray: any[], sourceArray: any[]) => [
...sourceArray,
...destinationArray.filter(d =>
sourceArray.every(s => !isEqual(d, s)),
Expand All @@ -221,18 +239,24 @@ export function initializeApollo(initialState = null) {
return _apolloClient;
}

export function addApolloState(client: any, pageProps: any) {
// Adds Apollo Client's state to pageProps
export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
pageProps: any,
) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
}

return pageProps;
}

// Custom React hook to use Apollo Client
export function useApollo(pageProps: any) {
const state = pageProps[APOLLO_STATE_PROP_NAME];

return useMemo(() => initializeApollo(state), [state]);
}

export const client = initializeApollo();
// Export the client instance
export const client: ApolloClient<NormalizedCacheObject> = initializeApollo();
6 changes: 6 additions & 0 deletions src/apollo/gql/gqlProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,3 +804,9 @@ export const FETCH_RECURRING_DONATIONS_BY_DATE = gql`
}
}
`;

export const DELETE_DRAFT_PROJECT = gql`
mutation ($projectId: Float!) {
deleteDraftProject(projectId: $projectId)
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { ONE_MONTH_SECONDS } from '@/lib/constants/constants';
import ExternalLink from '@/components/ExternalLink';
import { ChainType } from '@/types/config';
import NetworkLogo from '@/components/NetworkLogo';
import { EOrderBy } from '../../userProfile/UserProfile.view';
import { EOrderBy } from '../../userProfile/projectsTab/type';

const itemPerPage = 10;

Expand Down
13 changes: 0 additions & 13 deletions src/components/views/userProfile/UserProfile.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
isUserRegistered,
shortenAddress,
} from '@/lib/helpers';
import { EDirection } from '@/apollo/types/gqlEnums';
import ExternalLink from '@/components/ExternalLink';
import IncompleteProfileToast from '@/components/views/userProfile/IncompleteProfileToast';
import { useAppDispatch, useAppSelector } from '@/features/hooks';
Expand All @@ -45,18 +44,6 @@ import { IGiverPFPToken } from '@/apollo/types/types';
import { useProfileContext } from '@/context/profile.context';
import { useGeneralWallet } from '@/providers/generalWalletProvider';

export enum EOrderBy {
TokenAmount = 'TokenAmount',
UsdAmount = 'UsdAmount',
CreationDate = 'CreationDate',
Donations = 'Donations',
}

export interface IOrder {
by: EOrderBy;
direction: EDirection;
}

export interface IUserProfileView {}

const UserProfileView: FC<IUserProfileView> = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import { smallFormatDate, formatTxLink } from '@/lib/helpers';
import { slugToProjectView } from '@/lib/routeCreators';
import ExternalLink from '@/components/ExternalLink';
import { IWalletDonation } from '@/apollo/types/types';
import {
EOrderBy,
IOrder,
} from '@/components/views/userProfile/UserProfile.view';
import SortIcon from '@/components/SortIcon';
import DonationStatus from '@/components/badges/DonationStatusBadge';
import {
Expand All @@ -27,6 +23,7 @@ import {
import { Badge, EBadgeStatus } from '@/components/Badge';
import { formatDonation } from '@/helpers/number';
import NetworkLogo from '@/components/NetworkLogo';
import { EOrderBy, IOrder } from '../../projectsTab/type';

interface OneTimeDonationTable {
donations: IWalletDonation[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IWalletDonation } from '@/apollo/types/types';
import Pagination from '@/components/Pagination';
import NothingToSee from '@/components/views/userProfile/NothingToSee';
import DonationTable from '@/components/views/userProfile/donationsTab/oneTimeTab/OneTimeDonationsTable';
import { IOrder, EOrderBy } from '../../UserProfile.view';
import { EOrderBy, IOrder } from '../../projectsTab/type';
MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved
import { useProfileContext } from '@/context/profile.context';
import { WrappedSpinner } from '@/components/Spinner';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,50 @@ import styled from 'styled-components';
import { type FC } from 'react';
import { Button, Flex, IconTrash32, P } from '@giveth/ui-design-system';
import { useIntl } from 'react-intl';
import { useMutation } from '@tanstack/react-query';
import { IProject } from '@/apollo/types/types';
import { Modal } from '@/components/modals/Modal';
import { IModal } from '@/types/common';
import { useModalAnimation } from '@/hooks/useModalAnimation';
import { client } from '@/apollo/apolloClient';
import { DELETE_DRAFT_PROJECT } from '@/apollo/gql/gqlProjects';
import { useAppDispatch } from '@/features/hooks';
import { fetchUserByAddress } from '@/features/user/user.thunks';
import { useGeneralWallet } from '@/providers/generalWalletProvider';

interface IDeleteProjectModal extends IModal {
project: IProject;
refetchProjects: () => void;
}

const DeleteProjectModal: FC<IDeleteProjectModal> = ({
setShowModal,
project,
refetchProjects,
}) => {
const { formatMessage } = useIntl();
const { isAnimating, closeModal } = useModalAnimation(setShowModal);
const dispatch = useAppDispatch();
const { walletAddress } = useGeneralWallet();

const { mutate: deleteProject, isPending } = useMutation({
mutationFn: (projectId: number) =>
client.mutate({
mutation: DELETE_DRAFT_PROJECT,
variables: { projectId: projectId },
}),
// On success, refetch the user's projects
onSuccess: async () => {
await refetchProjects();
walletAddress &&
(await dispatch(fetchUserByAddress(walletAddress)));
closeModal();
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace 'isPending' with 'isLoading' to correctly reflect mutation state

The useMutation hook from @tanstack/react-query provides an isLoading property to indicate the loading state of the mutation, not isPending. Updating this will ensure the loading state is handled correctly in your component.

Apply this diff to fix the property name:

- const { mutate: deleteProject, isPending } = useMutation({
+ const { mutate: deleteProject, isLoading } = useMutation({

Also, update all references to isPending in your component to use isLoading.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { mutate: deleteProject, isPending } = useMutation({
mutationFn: (projectId: number) =>
client.mutate({
mutation: DELETE_DRAFT_PROJECT,
variables: { projectId: projectId },
}),
// On success, refetch the user's projects
onSuccess: async () => {
await refetchProjects();
walletAddress &&
(await dispatch(fetchUserByAddress(walletAddress)));
closeModal();
},
});
const { mutate: deleteProject, isLoading } = useMutation({
mutationFn: (projectId: number) =>
client.mutate({
mutation: DELETE_DRAFT_PROJECT,
variables: { projectId: projectId },
}),
// On success, refetch the user's projects
onSuccess: async () => {
await refetchProjects();
walletAddress &&
(await dispatch(fetchUserByAddress(walletAddress)));
closeModal();
},
});

MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved

const handleRemoveProject = async () => {
deleteProject(parseFloat(project.id));
};
MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved

return (
<Modal
Expand Down Expand Up @@ -44,7 +73,8 @@ const DeleteProjectModal: FC<IDeleteProjectModal> = ({
id: 'component.delete_project.yes',
})}
size='small'
onClick={() => setShowModal(true)}
onClick={handleRemoveProject}
loading={isPending}
MohammadPCh marked this conversation as resolved.
Show resolved Hide resolved
/>
<Button
buttonType='texty-gray'
Expand All @@ -53,6 +83,7 @@ const DeleteProjectModal: FC<IDeleteProjectModal> = ({
})}
size='small'
onClick={() => setShowModal(false)}
disabled={isPending}
/>
</Flex>
</ModalContainer>
Expand Down
Loading
Loading