Skip to content
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
682 changes: 17 additions & 665 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"@iexec/dataprotector": "0.1.2",
"@iexec/web3mail": "0.4.0",
"@leap-ai/sdk": "^0.0.119",
"@talentlayer/client": "^0.1.1",
"@web3modal/ethereum": "^2.7.1",
"@web3modal/react": "^2.7.1",
"@xmtp/xmtp-js": "^9.1.0",
Expand Down Expand Up @@ -64,10 +63,10 @@
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/jest": "^29.5.4",
"@types/level-js": "^4.0.2",
"@types/node": "^18.17.0",
"@types/react": "^18.0.23",
"@types/react-dom": "^18.0.7",
"@types/level-js": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"autoprefixer": "^10.4.12",
Expand All @@ -86,4 +85,4 @@
"engines": {
"node": ">=18.17"
}
}
}
130 changes: 130 additions & 0 deletions src/components/DisputeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { ITransaction, IUser, TransactionStatusEnum } from '../types';
import { arbitrationFeeTimeout, payArbitrationFee } from '../contracts/disputes';
import { usePublicClient, useWalletClient } from 'wagmi';
import { useRouter } from 'next/router';
import { useChainId } from '../hooks/useChainId';
import { useConfig } from '../hooks/useConfig';

function DisputeButton({
user,
transaction,
disabled,
arbitrationFee,
content,
}: {
user: IUser;
transaction: ITransaction;
disabled: boolean;
arbitrationFee: BigInt;
content?: string;
}) {
const chainId = useChainId();
const { data: walletClient } = useWalletClient({ chainId });
const publicClient = usePublicClient({ chainId });
const router = useRouter();
const transactionId = transaction?.id;
const config = useConfig();

const isSender = !!user && !!transaction && user.id === transaction.sender.id;

const isReceiver = !!user && !!transaction && user.id === transaction.receiver.id;

const userIsSenderAndHasPaid =
isSender && transaction.status === TransactionStatusEnum.WaitingReceiver;

const userIsSenderAndHasNotPaid =
isSender && transaction.status === TransactionStatusEnum.WaitingSender;

const userIsReceiverAndHasPaid =
isReceiver && transaction.status === TransactionStatusEnum.WaitingSender;

const userIsReceiverAndHasNotPaid =
isReceiver && transaction.status === TransactionStatusEnum.WaitingReceiver;

const noDispute = transaction.status === TransactionStatusEnum.NoDispute;

const disputeCreated = transaction.status === TransactionStatusEnum.DisputeCreated;

const disputeResolved = transaction.status === TransactionStatusEnum.Resolved;

const payFee = () => {
if (walletClient && arbitrationFee) {
return payArbitrationFee(
walletClient,
publicClient,
arbitrationFee,
isSender,
transactionId,
router,
config,
);
}
};
const timeout = () => {
if (walletClient) {
return arbitrationFeeTimeout(walletClient, publicClient, transactionId, router, config);
}
};

return (
<>
{userIsSenderAndHasNotPaid && (
<button
className={`ml-2 mt-4 px-5 py-2 border rounded-md hover:text-indigo-600 hover:bg-white border-indigo-600 bg-indigo-600 text-white'
}`}
onClick={() => payFee()}>
Pay fee
</button>
)}
{noDispute && (
<button
className={`ml-2 mt-4 px-5 py-2 border text-center ${
content
? 'cursor-pointer hover:text-indigo-600 hover:bg-white border-indigo-600 bg-indigo-600 text-white'
: 'text-gray-500 bg-gray-200'
} rounded-md border-grey-600`}
onClick={() => payFee()}>
{content ? content : 'No dispute'}
</button>
)}
{(userIsReceiverAndHasPaid || userIsSenderAndHasPaid) && (
<button
disabled={disabled}
className={`px-5 py-2 mt-4 border rounded-md ${
disabled
? 'text-gray-400 bg-gray-200'
: 'hover:text-indigo-600 hover:bg-white border-indigo-600 text-white bg-indigo-700'
}`}
onClick={() => timeout()}>
Timeout
</button>
)}
{userIsReceiverAndHasNotPaid && (
<button
className={`ml-2 mt-4 px-5 py-2 border rounded-md hover:text-indigo-600 hover:bg-white border-indigo-600 bg-indigo-600 text-white bg-indigo-700'
}`}
onClick={() => payFee()}>
Pay fee
</button>
)}
{disputeCreated && (
<span
className={
'ml-2 mt-4 px-5 py-2 border text-center text-gray-500 bg-gray-200 rounded-md border-grey-600'
}>
Waiting for arbitration...
</span>
)}
{disputeResolved && (
<span
className={
'ml-2 mt-4 px-5 py-2 border text-center text-gray-500 bg-gray-200 rounded-md border-grey-600'
}>
Dispute resolved
</span>
)}
</>
);
}

export default DisputeButton;
136 changes: 136 additions & 0 deletions src/components/Form/EvidenceForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { ErrorMessage, Field, Form, Formik } from 'formik';
import { useContext, useState } from 'react';
import * as Yup from 'yup';
import SubmitButton from './SubmitButton';
import FileDropper from '../../modules/Disputes/components/FileDropper';
import { postToIPFS } from '../../utils/ipfs';
import { showErrorTransactionToast } from '../../utils/toast';
import { generateEvidence } from '../../modules/Disputes/utils/dispute';
import TalentLayerContext from '../../context/talentLayer';
import { useWeb3Modal } from '@web3modal/react';
import { submitEvidence } from '../../contracts/disputes';
import { usePublicClient, useWalletClient } from 'wagmi';
import { useChainId } from '../../hooks/useChainId';
import { useConfig } from '../../hooks/useConfig';

interface IFormValues {
title: string;
about: string;
file: File | null;
}

const validationSchema = Yup.object({
title: Yup.string().required('Please provide a title for your evidence'),
about: Yup.string().required('Please provide a description of your evidence'),
file: Yup.mixed().required('Please provide a file'),
});

const initialValues: IFormValues = {
title: '',
about: '',
file: null,
};

function EvidenceForm({ transactionId }: { transactionId: string }) {
const { account, user } = useContext(TalentLayerContext);
const chainId = useChainId();
const { data: walletClient } = useWalletClient({ chainId });
const publicClient = usePublicClient({ chainId });
const { open: openConnectModal } = useWeb3Modal();
const [fileSelected, setFileSelected] = useState<File>();
const config = useConfig();

const onSubmit = async (
values: IFormValues,
{
setSubmitting,
resetForm,
}: { setSubmitting: (isSubmitting: boolean) => void; resetForm: () => void },
) => {
if (account?.isConnected === true && publicClient && walletClient && user?.id) {
try {
const fileExtension = values.file?.name.split('.').pop();
if (!values.file) return;
const arr = await values?.file.arrayBuffer();
const fileCid = await postToIPFS(arr);

const evidence = generateEvidence(
values.title,
values.about,
fileCid,
fileExtension as string,
);
const evidenceCid = await postToIPFS(JSON.stringify(evidence));

await submitEvidence(walletClient, publicClient, user?.id, transactionId, evidenceCid, chainId,config);
setSubmitting(false);
resetForm();
setFileSelected(undefined);
} catch (error) {
showErrorTransactionToast(error);
}
} else {
openConnectModal();
}
};

return (
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{({ isSubmitting, dirty, isValid }) => (
<>
<Form>
<h2 className=' mt-8 mb-2 text-gray-900 font-bold'>Add evidence:</h2>
<div className='flex flex-row justify-between gap-6 border border-gray-200 rounded-md p-8'>
<div className='flex flex-col grow'>
<label className='block'>
<span className='text-sm text-gray-500 font-bold'>Title</span>
<Field
type='text'
id='title'
name='title'
className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
placeholder=''
/>
<span className='text-red-500'>
<ErrorMessage name='title' />
</span>
</label>

<label className='block h-42'>
<span className='text-sm text-gray-500 font-bold'>About</span>
<Field
as='textarea'
id='about'
name='about'
className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
/>
<span className='text-red-500'>
<ErrorMessage name='about' />
</span>
</label>
</div>

<label className='flex flex-col'>
<FileDropper setFileSelected={setFileSelected} fileSelected={fileSelected} />

<Field type='hidden' id='file' name='file' value='' />
<span className='text-red-500'>
<ErrorMessage name='file' />
</span>
</label>
</div>
<div className='flex flex-row justify-between items-center mt-4'>
<SubmitButton
isSubmitting={isSubmitting}
disabled={!isValid || !dirty}
label='Submit evidence'
/>
</div>
</Form>
</>
)}
</Formik>
);
}

export default EvidenceForm;
24 changes: 20 additions & 4 deletions src/components/Form/ProposalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import SubmitButton from './SubmitButton';
import useTalentLayerClient from '../../hooks/useTalentLayerClient';
import usePlatform from '../../hooks/usePlatform';
import { chains } from '../../pages/_app';
import MetaEvidenceModal from '../../modules/Disputes/components/MetaEvidenceModal';

interface IFormValues {
about: string;
rateToken: string;
rateAmount: number;
expirationDate: number;
video_url: string;
videoUrl: string;
}

const validationSchema = Yup.object({
Expand Down Expand Up @@ -56,6 +57,7 @@ function ProposalForm({
const { platformHasAccess } = useContext(Web3MailContext);
const [aiLoading, setAiLoading] = useState(false);
const talentLayerClient = useTalentLayerClient();
const [conditionsValidated, setConditionsValidated] = useState(false);

const currentChain = chains.find(chain => chain.id === chainId);
const platform = usePlatform(process.env.NEXT_PUBLIC_PLATFORM_ID as string);
Expand Down Expand Up @@ -89,7 +91,7 @@ function ProposalForm({
rateToken: existingProposal?.rateToken.address || '',
rateAmount: existingRateTokenAmount || 0,
expirationDate: existingExpirationDate || 15,
video_url: existingProposal?.description?.video_url || '',
videoUrl: existingProposal?.description?.video_url || '',
};

const askAI = async (input: string, setFieldValue: any) => {
Expand Down Expand Up @@ -134,7 +136,7 @@ function ProposalForm({

const proposal = {
about: values.about,
video_url: values.video_url,
video_url: values.videoUrl,
};

let tx, cid, proposalResponse;
Expand Down Expand Up @@ -318,7 +320,21 @@ function ProposalForm({
{currentChain?.nativeCurrency.symbol}
</span>
)}
<SubmitButton isSubmitting={isSubmitting} label='Post' />
<div className='flex-col items-center mb-4'>
<MetaEvidenceModal
conditionsValidated={conditionsValidated}
setConditionsValidated={setConditionsValidated}
serviceData={service}
proposalData={values}
token={allowedTokenList.filter(token => token.address === values.rateToken)[0]}
seller={user}
/>
<SubmitButton
isSubmitting={isSubmitting}
disabled={!conditionsValidated}
label='Post'
/>
</div>
</div>
</Form>
)}
Expand Down
11 changes: 10 additions & 1 deletion src/components/Form/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { useAccount } from 'wagmi';
function SubmitButton({
isSubmitting,
label = 'Create',
disabled = false,
}: {
isSubmitting: boolean;
label?: string;
disabled?: boolean;
}) {
const { isConnected } = useAccount();
const { open: openConnectModal } = useWeb3Modal();
Expand Down Expand Up @@ -36,7 +38,14 @@ function SubmitButton({
Loading...
</button>
) : isConnected ? (
<button type='submit' className='grow px-5 py-2 rounded-xl bg-redpraha text-white'>
<button
type='submit'
disabled={disabled}
className={`px-5 py-2 border rounded-md ${
disabled
? 'text-gray-400 bg-gray-200'
: 'hover:text-indigo-600 hover:bg-white border-indigo-600 text-white bg-indigo-700'
}`}>
{label}
</button>
) : (
Expand Down
Loading