diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..e213eff --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,27 @@ +{ + "name": "@talentlayer/react", + "version": "0.0.1", + "description": "The TalentLayer Client for React with abstractions for easy interaction with the TalentLayer protocol", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "Apache-2.0", + "private": false, + "scripts": { + "lint": "eslint .", + "prepare": "npm run build", + "build": "tsc" + }, + "devDependencies": { + "@turbo/gen": "^1.10.12", + "@types/node": "^20.6.0", + "eslint-config-custom": "*", + "tsconfig": "*", + "typescript": "^5.2.2" + }, + "dependencies": { + "@talentlayer/client": "^0.1.7", + "net": "^1.0.2", + "react": "^18.2.0", + "wagmi": "^1.4.2" + } +} diff --git a/packages/react/src/components/TalentLayerError.tsx b/packages/react/src/components/TalentLayerError.tsx new file mode 100644 index 0000000..e7dfd1c --- /dev/null +++ b/packages/react/src/components/TalentLayerError.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +interface TalentLayerErrorProps { + error: any; +} + +export default function TalentLayerError(props: TalentLayerErrorProps) { + const { error } = props; + + return ( +
+

TalentLayer Error

+ {(() => { + try { + return JSON.stringify(error, null, 4); + } catch { + return `Error : ${error}`; + } + })()} +
+ ); +} diff --git a/packages/react/src/contexts/talentLayerContext.tsx b/packages/react/src/contexts/talentLayerContext.tsx new file mode 100644 index 0000000..49d5bf0 --- /dev/null +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -0,0 +1,116 @@ +'use client'; + +import React, { ContextType } from 'react'; +import { createContext, ReactNode, useEffect, useMemo, useState } from 'react'; +import { useAccount } from 'wagmi'; +import { IAccount, IUser, NetworkEnum } from '../types'; +import { TalentLayerClient } from '@talentlayer/client'; + +interface TalentLayerProviderProps { + children: ReactNode; + config: ConstructorParameters[0] & { + account: ReturnType; + }; +} + +export interface Subgraph { + query: (query: string) => any; +} + +const TalentLayerContext = createContext<{ + user?: IUser; + account?: IAccount; + refreshData: () => Promise; + loading: boolean; + client: TalentLayerClient | undefined; + platformId: number; + chainId?: NetworkEnum; + subgraph: Subgraph; +}>({ + user: undefined, + account: undefined, + refreshData: async () => { + return false; + }, + loading: true, + client: {} as TalentLayerClient, + platformId: -1, + chainId: undefined, + subgraph: { query: _ => new Promise(resolve => resolve(null)) }, +}); + +export function TalentLayerProvider(props: TalentLayerProviderProps) { + const { children, config } = props; + const { chainId, platformId } = config; + + const account = config.account; + + const [user, setUser] = useState(); + const [loading, setLoading] = useState(true); + + const [talentLayerClient, setTalentLayerClient] = useState(); + + async function loadData() { + if (!talentLayerClient) return false; + + if (!account.address || !account.isConnected) { + setLoading(false); + return false; + } + + try { + const userResponse = await talentLayerClient.profile.getByAddress(account.address); + + const currentUser = userResponse; + + const platformResponse = await talentLayerClient.platform.getOne( + config.platformId.toString(), + ); + + const platform = platformResponse; + currentUser.isAdmin = platform?.address === currentUser?.address; + + setUser(currentUser); + + return true; + } catch (err: any) { + console.error(err); + + return false; + } finally { + setLoading(false); + } + } + + useEffect(() => { + if (chainId && account.address && !talentLayerClient) { + const tlClient = new TalentLayerClient(config); + setTalentLayerClient(tlClient); + } + }, [chainId, account.address]); + + useEffect(() => { + if (!talentLayerClient) return; + + loadData(); + }, [talentLayerClient]); + + const value = useMemo>(() => { + return { + user, + account: account ? account : undefined, + refreshData: loadData, + loading, + client: talentLayerClient, + chainId, + platformId, + subgraph: { + query: (query: string) => (talentLayerClient as TalentLayerClient).graphQlClient.get(query), + }, + } as any; + }, [account.address, user?.id, loading]); + + return {children}; +} + +export default TalentLayerContext; diff --git a/packages/react/src/hooks/useFees.ts b/packages/react/src/hooks/useFees.ts new file mode 100644 index 0000000..c24d690 --- /dev/null +++ b/packages/react/src/hooks/useFees.ts @@ -0,0 +1,49 @@ +import { useEffect, useState } from 'react'; +import { IFees } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useFees( + originServicePlatformId: string, + originValidatedProposalPlatformId: string, +) { + const [fees, setFees] = useState({ + protocolEscrowFeeRate: 0, + originServiceFeeRate: 0, + originValidatedProposalFeeRate: 0, + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + const fees: IFees = { + protocolEscrowFeeRate: 0, + originServiceFeeRate: 0, + originValidatedProposalFeeRate: 0, + }; + try { + const response = await talentLayer.client?.escrow.getProtocolAndPlatformsFees(originServicePlatformId, originValidatedProposalPlatformId) + + if (response) { + fees.protocolEscrowFeeRate = response.protocols[0].protocolEscrowFeeRate; + fees.originServiceFeeRate = response.servicePlatform.originServiceFeeRate; + fees.originValidatedProposalFeeRate = response.proposalPlatform.originValidatedProposalFeeRate; + } + + setFees(fees); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [originServicePlatformId, originValidatedProposalPlatformId]); + + return [fees, loading, error] as const; +} diff --git a/packages/react/src/hooks/useMintFee.ts b/packages/react/src/hooks/useMintFee.ts new file mode 100644 index 0000000..cd42e4f --- /dev/null +++ b/packages/react/src/hooks/useMintFee.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import useTalentLayer from './useTalentLayer'; + +export default function useMintFee() { + const [mintFee, setMintFee] = useState(0); + const [shortHandlesMaxPrice, setShortHandlesMaxPrice] = useState(0); + + const talentLayer = useTalentLayer(); + + async function loadData() { + try { + const response = await talentLayer.client?.profile.getMintFees(); + + if (response) { + const protocol = response.protocols[0]; + setMintFee(protocol.userMintFee); + setShortHandlesMaxPrice(protocol.shortHandlesMaxPrice); + } + } catch (err: any) { + // eslint-disable-next-line no-console + console.error(err); + } + } + + useEffect(() => { + loadData(); + }, [talentLayer.chainId]); + + const calculateMintFee = (handle: string) => { + const length = handle.length; + const handlePrice = length > 4 ? mintFee : shortHandlesMaxPrice / Math.pow(2, length - 1); + return handlePrice; + }; + + return { calculateMintFee }; +} diff --git a/packages/react/src/hooks/usePaymentsByService.ts b/packages/react/src/hooks/usePaymentsByService.ts new file mode 100644 index 0000000..aa4e43d --- /dev/null +++ b/packages/react/src/hooks/usePaymentsByService.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { IPayment, PaymentTypeEnum } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function usePaymentsByService(id: string, paymentType?: PaymentTypeEnum) { + const [payments, setPayments] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(true); + + const talentLayer = useTalentLayer(); + + async function loadData() { + try { + const response = await talentLayer.client?.escrow.getByService(id) + + if (response) { + setPayments(response); + } + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [id]); + + return [payments, loading, error] as const; +} diff --git a/packages/react/src/hooks/usePaymentsForUser.ts b/packages/react/src/hooks/usePaymentsForUser.ts new file mode 100644 index 0000000..a9f1f5a --- /dev/null +++ b/packages/react/src/hooks/usePaymentsForUser.ts @@ -0,0 +1,72 @@ +import { useEffect, useState } from 'react'; +import { IPayment } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function usePaymentsForUser(options: { + id: string; + numberPerPage: number; + startDate?: string; + endDate?: string; +}) { + const { id, numberPerPage } = options; + const startDate = options.startDate; + const endDate = options.endDate; + + const [payments, setPayments] = useState([]); + const [hasMoreData, setHasMoreData] = useState(true); + const [offset, setOffset] = useState(1); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + const total = offset * numberPerPage; + + const start = startDate ? new Date(startDate).getTime() / 1000 : ''; + const end = endDate ? new Date(endDate).getTime() / 1000 : ''; + + async function loadData() { + if (!talentLayer.client) return; + + setLoading(true); + try { + const response = await talentLayer.client?.profile.getPayments( + options.id, + options.numberPerPage, + offset, + options.startDate, + options.endDate, + ); + + if (response) { + setPayments([...response]); + + if (response.length < total) { + setHasMoreData(false); + } + } + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [total, id, start, end]); + + useEffect(() => { + if (!!start && !!end) { + setOffset(1); + setHasMoreData(true); + } + }, [start, end]); + + function loadMore() { + setOffset(offset + 1); + } + + return [{ items: payments, hasMoreData, loadMore } as const, loading, error] as const; +} diff --git a/packages/react/src/hooks/usePlatform.ts b/packages/react/src/hooks/usePlatform.ts new file mode 100644 index 0000000..0aed553 --- /dev/null +++ b/packages/react/src/hooks/usePlatform.ts @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react'; +import { IPlatform } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function usePlatform(id?: string) { + const [platforms, setAddresses] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + + if (!talentLayer.client) return; + + try { + const response = await talentLayer.client.platform.getOne( + id || talentLayer.platformId.toString(), + ); + + if (response) setAddresses(response); + else throw new Error('Unable to find platform'); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [id]); + + return [platforms, loading, error] as const; +} diff --git a/packages/react/src/hooks/useProposal.ts b/packages/react/src/hooks/useProposal.ts new file mode 100644 index 0000000..c5c371d --- /dev/null +++ b/packages/react/src/hooks/useProposal.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import { IProposal } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useProposal(proposalId: string) { + const [data, setData] = useState(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + if (!talentLayer.client) return; + + try { + if (proposalId !== undefined) { + const proposal = await talentLayer.client.proposal.getOne(proposalId); + return setData(proposal); + } + + throw new Error('Proposal not found'); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [proposalId]); + + return [data, loading, error] as const; +} diff --git a/packages/react/src/hooks/useProposals.ts b/packages/react/src/hooks/useProposals.ts new file mode 100644 index 0000000..291c880 --- /dev/null +++ b/packages/react/src/hooks/useProposals.ts @@ -0,0 +1,49 @@ +import { useEffect, useState } from 'react'; +import { IProposal, OnlyOne } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useProposals( + filter?: OnlyOne<{ + serviceId?: string; + userId?: string; + }>, +) { + const [data, setData] = useState(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + if (!talentLayer.client) return; + + try { + if (filter === undefined) return setData(undefined); + + if (filter.serviceId !== undefined) { + const proposals = await talentLayer.client.proposal.getByServiceId(filter.serviceId); + return setData(proposals.data.proposals as IProposal[]); + } + + if (filter.userId !== undefined) { + const proposals = await talentLayer.client.proposal.getByUser(filter.userId); + + return setData(proposals.data.proposals as IProposal[]); + } + + throw new Error('Proposal not found'); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, []); + + return [data, loading, error] as const; +} diff --git a/packages/react/src/hooks/useReviews.ts b/packages/react/src/hooks/useReviews.ts new file mode 100644 index 0000000..edf30d1 --- /dev/null +++ b/packages/react/src/hooks/useReviews.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; +import { IReview } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useReviews(serviceId: string) { + const [data, setData] = useState(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + if (!talentLayer.client) return; + + try { + const reviews = await talentLayer.client.review.getByService(serviceId); + + return setData(reviews.data); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, []); + + return [data, loading, error] as const; +} diff --git a/packages/react/src/hooks/useService.ts b/packages/react/src/hooks/useService.ts new file mode 100644 index 0000000..d7b986a --- /dev/null +++ b/packages/react/src/hooks/useService.ts @@ -0,0 +1,32 @@ +import { useState, useEffect } from 'react'; +import { IService } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useService(serviceId: string) { + const [service, setService] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + setLoading(true); + if (!talentLayer.client) return; + + try { + const response = await talentLayer.client.service.getOne(serviceId); + setService(response); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, [serviceId]); + + return [service, loading, error]; +} diff --git a/packages/react/src/hooks/useServices.ts b/packages/react/src/hooks/useServices.ts new file mode 100644 index 0000000..715d5a2 --- /dev/null +++ b/packages/react/src/hooks/useServices.ts @@ -0,0 +1,70 @@ +import { useEffect, useState } from 'react'; +import { IService, ServiceStatusEnum } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useServices(filters: { + serviceStatus?: ServiceStatusEnum; + buyerId?: string; + sellerId?: string; + searchQuery?: string; + numberPerPage?: number; + platformId?: string; +}) { + const [services, setServices] = useState([]); + const [canLoadMore, setCanLoadMore] = useState(true); + const [loading, setLoading] = useState(false); + const [offset, setOffset] = useState(0); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + if (!talentLayer.client) return; + + try { + setLoading(true); + let response; + let newServices: IService[] = []; + + response = await talentLayer.client.service.getServices({ + offset, + ...filters, + platformId: filters.platformId || talentLayer.platformId.toString(), + }); + + if (filters.searchQuery) { + newServices = response.data; + } else { + newServices = response.data.services; + } + + setServices(offset === 0 ? newServices || [] : [...services, ...newServices]); + + if (filters.numberPerPage && newServices.length < filters.numberPerPage) { + setCanLoadMore(false); + } else { + setCanLoadMore(true); + } + } catch (err: any) { + console.error(err); + setError(err); + } finally { + setLoading(false); + } + } + + useEffect(() => { + setServices([]); + setOffset(0); + }, [filters.searchQuery]); + + useEffect(() => { + loadData(); + }, [filters.numberPerPage, offset, filters.searchQuery, filters.buyerId, filters.serviceStatus]); + + function loadMore() { + filters.numberPerPage ? setOffset(offset + filters.numberPerPage) : ''; + } + + return [{ canLoadMore, items: services, loadMore } as const, loading, error] as const; +} diff --git a/packages/react/src/hooks/useTalentLayer.ts b/packages/react/src/hooks/useTalentLayer.ts new file mode 100644 index 0000000..abb5f80 --- /dev/null +++ b/packages/react/src/hooks/useTalentLayer.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import TalentLayerContext from "../contexts/talentLayerContext"; + +export default function useTalentLayer() { + return useContext(TalentLayerContext); +} diff --git a/packages/react/src/hooks/useUser.ts b/packages/react/src/hooks/useUser.ts new file mode 100644 index 0000000..1331cf3 --- /dev/null +++ b/packages/react/src/hooks/useUser.ts @@ -0,0 +1,40 @@ +import { useEffect, useState } from 'react'; +import { IUser, OnlyOne } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useUser(options: OnlyOne<{ userId: string; address: `0x${string}` }>) { + const [user, setUser] = useState(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + if (!talentLayer.client) return; + + try { + if (options.userId) { + const response = await talentLayer.client.profile.getById(options.userId); + setUser(response); + } + + if (options.address) { + const response = await talentLayer.client.profile.getByAddress(options.address); + setUser(response); + } + + setLoading(true); + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + loadData(); + }, []); + + return [user, loading, error] as const; +} diff --git a/packages/react/src/hooks/useUsers.ts b/packages/react/src/hooks/useUsers.ts new file mode 100644 index 0000000..1380b6c --- /dev/null +++ b/packages/react/src/hooks/useUsers.ts @@ -0,0 +1,54 @@ +import { useEffect, useState } from 'react'; +import { IUser } from '../types'; +import useTalentLayer from './useTalentLayer'; + +export default function useUsers(options: { searchQuery?: string; numberPerPage?: number }) { + const [users, setUsers] = useState([]); + const [hasMoreData, setHasMoreData] = useState(true); + const [offset, setOffset] = useState(0); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const talentLayer = useTalentLayer(); + + async function loadData() { + if (!talentLayer.client) return; + + try { + setLoading(true); + const response = await talentLayer.client.profile.getBy({}); + + if (offset === 0) { + setUsers(response.data.users || []); + } else { + setUsers([...users, ...response.data.users]); + } + + if (options.numberPerPage && response?.data?.users.length < options.numberPerPage) { + setHasMoreData(false); + } else { + setHasMoreData(true); + } + } catch (error: any) { + console.error(error); + setError(error); + } finally { + setLoading(false); + } + } + + useEffect(() => { + setUsers([]); + setOffset(0); + }, [options.searchQuery]); + + useEffect(() => { + loadData(); + }, [options.numberPerPage, options.searchQuery, offset]); + + function loadMore() { + options.numberPerPage ? setOffset(offset + options.numberPerPage) : ''; + } + + return [{ items: users, hasMoreData, loadMore } as const, loading, error] as const; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 0000000..056637d --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,31 @@ +'use client'; + +import { TalentLayerProvider } from './contexts/talentLayerContext'; +import useTalentLayer from './hooks/useTalentLayer'; +import useServices from './hooks/useServices'; +import useService from './hooks/useService'; +import usePlatform from './hooks/usePlatform'; +import useProposals from './hooks/useProposals'; +import useProposal from './hooks/useProposal'; +import useFees from './hooks/useFees'; +import useMintFee from './hooks/useMintFee'; +import useUsers from './hooks/useUsers'; +import useUser from './hooks/useUser'; +import usePaymentsByService from './hooks/usePaymentsByService'; +import usePaymentsForUser from './hooks/usePaymentsForUser'; + +export { + TalentLayerProvider, + useTalentLayer, + useServices, + useService, + usePlatform, + useProposal, + useProposals, + useFees, + useMintFee, + useUsers, + useUser, + usePaymentsByService, + usePaymentsForUser, +}; diff --git a/packages/react/src/types/index.ts b/packages/react/src/types/index.ts new file mode 100644 index 0000000..3d1a044 --- /dev/null +++ b/packages/react/src/types/index.ts @@ -0,0 +1,329 @@ +import { Connector } from 'wagmi'; + +export type IHive = { + id: string; + handle: string; + address: string; + description?: IHiveDetails; + members: string[]; + honeyFee: number; + owner: string; + ownerIdentity?: IUser; + identity: IUser; + paymasterAddress: string; +}; + +export type IHiveDetails = { + manifesto: string; + offeredServices: string; +}; + +export type IUser = { + id: string; + handle: string; + address: string; + rating: string; + description?: IUserDetails; + userStats: IUserStats; + delegates?: string[]; + isAdmin?: boolean; +}; + +export type IUserDetails = { + id: string; + title: string; + name: string; + role?: string; + image_url?: string; + video_url?: string; + about?: string; + skills_raw?: string; + user: IUser; + web3mailPreferences?: IWeb3mailPreferences; +}; +export type IWeb3mailPreferences = { + activeOnNewService: boolean; + activeOnNewProposal: boolean; + activeOnProposalValidated: boolean; + activeOnFundRelease: boolean; + activeOnReview: boolean; + activeOnPlatformMarketing: boolean; +}; + +export type IUserStats = { + numReceivedReviews: number; + numGivenReviews?: number; + numCreatedServices?: number; + numFinishedServicesAsBuyer?: number; + numCreatedProposals?: number; + numFinishedServicesAsSeller?: number; +}; + +export type IAccount = { + address?: `0x${string}`; + connector?: Connector; + isConnecting: boolean; + isReconnecting: boolean; + isConnected: boolean; + isDisconnected: boolean; + status: 'connecting' | 'reconnecting' | 'connected' | 'disconnected'; +}; + +export type ICompletionScores = { + total: ICompletionScore; + userDetails: ICompletionScore; + web3mail: ICompletionScore; +}; + +export type ICompletionScore = { + score: number; + totalPoint: number; + percentage: number; +}; + +// TODO: add the rest of the fields +export type ITransaction = { + id: string; +}; + +export type IService = { + id: string; + status: ServiceStatusEnum; + buyer: IUser; + seller: IUser; + sender: IUser; + recipient: IUser; + cid: string; + createdAt: string; + updatedAt: string; + transaction: ITransaction; + platform: IPlatform; + proposals: IProposal[]; + validatedProposal: IProposal[]; + description?: IServiceDetails; +}; + +export type IFeeType = { + platform: IPlatform; + OriginPlatform: IPlatform; +}; + +export type IFeePayment = { + id: string; // autogenerated id + createdAt: string; // timestamp of block creation + platform: IPlatform; // platform entity + service: IService; // service entity + type: IFeeType; // fee type + token: IToken; // token entity + amount: string; // platform fee +}; + +export type IFeeClaim = { + id: string; // autogenerated id + createdAt: string; // timestamp of block creation + platform: IPlatform; // platform entity + token: IToken; // token entity + amount: string; // claim amount + transactionHash: string; // Transaction hash of the transfer +}; + +export type IPlatform = { + id: string; // platform id + address: `0x${string}`; // wallet address of platform owner + name: string; // name of the platform + createdAt: string; + updatedAt: string; + feePayments: IFeePayment[]; // Platform-related fee payments + totalPlatformGains: IPlatformGain; // Platform-related total gains per token + feeClaims: IFeeClaim[]; // Platform-related fee claims + originServiceFeeRate: number; // Fee asked by the platform which created the service + originValidatedProposalFeeRate: number; // Fee asked by the platform on which the proposal was validated + servicePostingFee: string; // Flat fee asked by the platform to post a service + proposalPostingFee: string; // Flat fee asked by the platform to post a proposal + arbitrator: `0x${string}`; // address of the arbitrator contract + arbitratorExtraData: `0x${string}`; // extra data for the arbitrator + arbitrationFeeTimeout: string; // timeout for the arbitration fee to be paid + cid: string; //cid of description + description: IPlatformDescription; + signer: `0x${string}`; // address of the platform signer +}; + +export type IPlatformDescription = { + id: string; // cid + name: string; // Not implement yet on 06.09.23 + about: string; // text + website: string; // url + platform: IPlatform; + video_url: string; + image_url: string; +}; + +export type IPlatformGain = { + id: string; + platform: IPlatform; + token: IToken; + totalPlatformFeeGain: string; + totalOriginPlatformFeeGain: string; +}; + +export enum MintStatusEnum { + ON_PAUSE, + ONLY_WHITELIST, + PUBLIC, +} + +export type IKeyword = { + id: string; +}; + +export type IServiceDetails = { + id: string; + title?: string; + about?: string; + keywords: IKeyword[]; + rateAmount?: string; + rateToken?: string; + keywords_raw?: string; + startDate?: string; + expectedEndDate?: string; +}; + +export type IServiceDetailsBuyer = { + title: string; + about: string; + rateAmount: string; + rateToken: string; + buyerHandle: string; + buyerId: string; + buyerServiceCount: string; + buyerRating: string; + serviceId: string; + createdAt: string; + updatedAt: string; +}; + +export type IReview = { + id: string; + service: IService; + to: IUser; + uri: string; + rating: number; + createdAt: string; + description?: IReviewDetails; +}; + +export type IReviewDetails = { + id: string; + content: string; +}; + +export enum ServiceStatusEnum { + Opened = 'Opened', + Confirmed = 'Confirmed', + Finished = 'Finished', + Cancelled = 'Cancelled', + Uncompleted = 'Uncompleted', +} + +export enum ProposalStatusEnum { + Pending = 'Pending', + Validated = 'Validated', + Rejected = 'Rejected', +} + +export type IProposalDetails = { + id: string; + title: string; + about: string; + startDate: string; + expectedHours: string; + service: IService; + expirationDate: string; + video_url?: string; +}; + +export type IProposal = { + id: string; + cid: string; + status: ProposalStatusEnum; + seller: IUser; + buyer: IUser; + rateToken: IToken; + rateAmount: string; + service: IService; + // transaction: ITransaction; + platform: IPlatform; + createdAt: string; + updatedAt: string; + description?: IProposalDetails; + expirationDate: string; +}; + +export type IFees = { + protocolEscrowFeeRate: number; + originServiceFeeRate: number; + originValidatedProposalFeeRate: number; +}; + +export enum ProposalTypeEnum { + Hourly = 1, + Flat, + Milestone, +} + +export enum ProfileTypeEnum { + Buyer = 1, + Seller, +} + +export enum PaymentTypeEnum { + Release = 'Release', + Reimburse = 'Reimburse', +} + +export enum NetworkEnum { + LOCAL = 1337, + MUMBAI = 80001, + IEXEC = 134, +} + +export type IToken = { + name: string; + address: `0x${string}`; + symbol: string; + decimals: number; + minimumTransactionAmount?: string; +}; + +export type ITokenFormattedValues = { + roundedValue: string; + exactValue: string; +}; + +export type IPayment = { + createdAt: number; + id: string; + amount: string; + rateToken: IToken; + paymentType: PaymentTypeEnum; + transactionHash: string; + service: IService; +}; + +export type IUserGain = { + id: string; + user: IUser; + token: IToken; + totalGain: string; +}; + +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void + ? I + : never; + +export type OnlyOne = Pick> & + { + [K in Keys]-?: Required> & Partial, undefined>>; + }[Keys]; diff --git a/packages/react/src/utils/index.ts b/packages/react/src/utils/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000..649ef00 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "tsconfig/base.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"], + "compilerOptions": { + "jsx": "react", + "outDir": "dist", + "lib": [ "ES2023", "DOM"] + } +}