Skip to content

Commit

Permalink
Merge pull request #25 from bmschwartz/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
bmschwartz authored Aug 15, 2024
2 parents 248c349 + 858cdf3 commit dc365a9
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 185 deletions.
1 change: 0 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export { default as LoadingOverlay } from './common/LoadingOverlay';
export { default as FullScreenLoader } from './common/FullScreenLoader';

export { default as RequireWallet } from './wallet/RequireWallet';
export { default as ConnectWalletDialog } from './wallet/ConnectWalletDialog';
export { default as ConnectWalletButton } from './wallet/ConnectWalletButton';

export { default as RequestList } from './request/RequestList';
Expand Down
9 changes: 3 additions & 6 deletions src/components/wallet/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { useWalletInfo, useWeb3Modal, useWeb3ModalAccount } from '@web3modal/ethers/react';

import { FMPButton } from '@/components';
import { useWallet } from '@/hooks/useWallet';

const ConnectWalletButton: React.FC = () => {
const { open } = useWeb3Modal();
const { isConnected } = useWeb3ModalAccount();
const { walletInfo } = useWalletInfo();
const { isConnected, connectWallet } = useWallet();

return (
<FMPButton onClick={() => open()} sx={{ marginTop: '16px' }}>
<FMPButton onClick={() => connectWallet()} sx={{ marginTop: '16px' }}>
{isConnected ? 'Disconnect Wallet' : 'Connect Wallet'}
</FMPButton>
);
Expand Down
94 changes: 0 additions & 94 deletions src/components/wallet/ConnectWalletDialog.tsx

This file was deleted.

108 changes: 108 additions & 0 deletions src/contexts/RequestDetailContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { createContext, ReactNode, useEffect, useState } from 'react';

import { execute, GetPictureRequestDocument } from '@/graphql/client';
import { CreateRequestCommentParams, useComments } from '@/hooks/useComments';
import { useIpfs } from '@/hooks/useIpfs';
import { CreateRequestSubmissionParams, useSubmissions } from '@/hooks/useSubmissions';
import { RequestComment } from '@/types/comment';
import { Request } from '@/types/request';
import { RequestSubmission } from '@/types/submission';
import { mapPictureRequest } from '@/utils/mappers';

export interface RequestDetailContextType {
request: Request | null;
comments: RequestComment[];
submissions: RequestSubmission[];
loading: boolean;
fetchRequest: (id: string) => void;
createComment: (params: CreateRequestCommentParams) => Promise<RequestComment>;
createSubmission: (params: CreateRequestSubmissionParams) => Promise<RequestSubmission>;
}

interface RequestDetailProviderProps {
children: ReactNode;
requestId: string;
}

export const RequestDetailContext = createContext<RequestDetailContextType | undefined>(undefined);

export const RequestDetailProvider = ({ children, requestId }: RequestDetailProviderProps) => {
const [loading, setLoading] = useState<boolean>(true);
const [request, setRequest] = useState<Request | null>(null);
const [comments, setComments] = useState<RequestComment[]>([]);
const [submissions, setSubmissions] = useState<RequestSubmission[]>([]);

const { fetchIPFSData } = useIpfs();
const { createRequestComment, pollForNewComment, fetchComments } = useComments();
const { createRequestSubmission, pollForNewSubmission, fetchSubmissions } = useSubmissions();

const fetchRequest = async (id: string) => {
try {
const result = await execute(GetPictureRequestDocument, { id });
const request = result?.data?.pictureRequest;
if (request) {
const ipfsData = await fetchIPFSData(request.ipfsHash);
return mapPictureRequest({ ...request, ...ipfsData });
}
} catch (error) {
console.error('Error fetching request:', error);
}
};

useEffect(() => {
const fetchAllRequestDetails = async () => {
if (requestId) {
setLoading(true);

const [request, comments, submissions] = await Promise.all([
fetchRequest(requestId),
fetchComments(requestId),
fetchSubmissions(requestId),
]);

if (request) {
setRequest(request);
}
setComments(comments);
setSubmissions(submissions);
setLoading(false);
}
};

fetchAllRequestDetails();
}, [requestId]);

const createSubmission = async (params: CreateRequestSubmissionParams): Promise<RequestSubmission> => {
const optimisticSubmission = await createRequestSubmission(params);
setSubmissions((prevSubmissions) => [...prevSubmissions, optimisticSubmission]);

pollForNewSubmission(optimisticSubmission.id, (polledSubmission: RequestSubmission) => {
setSubmissions((prevSubmissions) =>
prevSubmissions.map((submission) => (submission.id === optimisticSubmission.id ? polledSubmission : submission))
);
});

return optimisticSubmission;
};

const createComment = async (params: CreateRequestCommentParams): Promise<RequestComment> => {
const optimisticComment = await createRequestComment(params);
setComments((prevComments) => [...prevComments, optimisticComment]);

pollForNewComment(optimisticComment.id, (polledComment: RequestComment) => {
setComments((prevComments) =>
prevComments.map((comment) => (comment.id === optimisticComment.id ? polledComment : comment))
);
});

return optimisticComment;
};

return (
<RequestDetailContext.Provider
value={{ request, comments, submissions, loading, createComment, createSubmission, fetchRequest }}
>
{children}
</RequestDetailContext.Provider>
);
};
64 changes: 36 additions & 28 deletions src/hooks/useComments.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
import { useState } from 'react';

import { execute, GetRequestCommentDocument, GetRequestCommentsDocument } from '@/graphql/client';
import { useContractService } from '@/hooks/useContractService';
import { CreateRequestCommentParams as ContractCreateCommentParams } from '@/services/contractService';
import { RequestComment } from '@/types/comment';
import { pollWithRetry } from '@/utils/delay';
import { mapRequestComment } from '@/utils/mappers';
import { useIpfs } from './useIpfs';

interface CreateRequestCommentParams extends Omit<ContractCreateCommentParams, 'requestAddress' | 'ipfsHash'> {
export interface CreateRequestCommentParams extends Omit<ContractCreateCommentParams, 'requestAddress' | 'ipfsHash'> {
text: string;
requestId: string;
setStatus?: (status: string) => void;
}

export const useComments = () => {
const { fetchIPFSData } = useIpfs();
const { uploadRequestComment } = useIpfs();
const { contractService } = useContractService();

const [loading, setLoading] = useState<boolean>(false);
const { fetchIPFSData, uploadRequestComment } = useIpfs();

const loadIPFSAndTransform = async (comment: any) => {
const ipfsData = await fetchIPFSData(comment.ipfsHash);
return { ...comment, ...ipfsData };
};

const fetchComments = async (requestId: string) => {
const result = await execute(GetRequestCommentsDocument, { requestId });
const comments = result?.data?.requestComments || [];
const transformedComments = await Promise.all(comments.map(loadIPFSAndTransform));
return transformedComments.map(mapRequestComment);
const fetchComments = async (requestId: string): Promise<RequestComment[]> => {
try {
const result = await execute(GetRequestCommentsDocument, { requestId });
const comments = result?.data?.requestComments || [];
const transformedComments = await Promise.all(comments.map(loadIPFSAndTransform));
return transformedComments.map(mapRequestComment);
} catch (e) {
console.error('Error fetching comments:', e);
return [];
}
};

const pollForNewComment = async (id: string): Promise<void> => {
return pollWithRetry({
const pollForNewComment = async (id: string, onCommentFound: (comment: RequestComment) => void): Promise<void> => {
const fetchedComment = await pollWithRetry({
callback: async () => {
const result = await execute(GetRequestCommentDocument, { id });
return result?.data?.requestComment || null;
},
});

if (!fetchedComment) {
return;
}

const commentWithIpfData = await loadIPFSAndTransform(fetchedComment);
const finalComment = mapRequestComment(commentWithIpfData);

onCommentFound(finalComment);
};

const createRequestComment = async ({
Expand All @@ -47,9 +57,7 @@ export const useComments = () => {
account,
requestId,
setStatus,
}: CreateRequestCommentParams): Promise<void> => {
setLoading(true);

}: CreateRequestCommentParams): Promise<RequestComment> => {
try {
setStatus?.('Uploading comment...');
const ipfsHash = await uploadRequestComment({ text });
Expand All @@ -62,22 +70,22 @@ export const useComments = () => {
requestAddress: requestId,
});

let created = false;
if (requestCommentAddress) {
// Try to fetch data from the subgraph until the new comment appears
setStatus?.('Waiting for confirmation...');
await pollForNewComment(requestCommentAddress);
created = true;
}

if (!created) {
if (!requestCommentAddress) {
throw new Error('Failed to create request comment');
}

const optimisticComment: RequestComment = {
id: requestCommentAddress,
text,
commenter: account,
createdAt: Math.floor(Date.now() / 1000),
};

return optimisticComment;
} finally {
setStatus?.('');
setLoading(false);
}
};

return { createRequestComment, fetchComments, loading };
return { createRequestComment, fetchComments, pollForNewComment };
};
11 changes: 11 additions & 0 deletions src/hooks/useRequestDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useContext } from 'react';

import { RequestDetailContext, RequestDetailContextType } from '@/contexts/RequestDetailContext';

export const useRequestDetail = (): RequestDetailContextType => {
const context = useContext(RequestDetailContext);
if (!context) {
throw new Error('useRequestDetail must be used within a RequestDetailProvider');
}
return context;
};
Loading

0 comments on commit dc365a9

Please sign in to comment.