From c89c2e051c919a1a8c53bdffe767131d9e1dffa7 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Wed, 27 Sep 2023 16:03:31 +0530 Subject: [PATCH 01/10] Initial setup for React client --- packages/react/package.json | 26 ++++++++++++++++++++++++++ packages/react/src/index.ts | 0 packages/react/tsconfig.json | 9 +++++++++ 3 files changed, 35 insertions(+) create mode 100644 packages/react/package.json create mode 100644 packages/react/src/index.ts create mode 100644 packages/react/tsconfig.json diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..dcb1f71 --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,26 @@ +{ + "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": "MIT", + "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": { + "axios": "^1.5.0", + "react": "^18.2.0", + "wagmi": "^1.4.2" + } +} diff --git a/packages/react/src/index.ts b/packages/react/src/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..22521ef --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/base.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"], + "compilerOptions": { + "jsx": "react", + "outDir": "dist", + } +} From 2580d844087333ce83b4f92a2ef23888c22655a1 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Wed, 27 Sep 2023 16:19:50 +0530 Subject: [PATCH 02/10] Added talentLayerContext, a provider for it and custom hook to call it --- .../react/src/contexts/talentLayerContext.tsx | 120 +++++++ packages/react/src/hooks/useTalentLayer.tsx | 6 + packages/react/src/temp.ts | 48 +++ packages/react/src/types/index.ts | 316 ++++++++++++++++++ packages/react/src/utils/profile.ts | 85 +++++ 5 files changed, 575 insertions(+) create mode 100644 packages/react/src/contexts/talentLayerContext.tsx create mode 100644 packages/react/src/hooks/useTalentLayer.tsx create mode 100644 packages/react/src/temp.ts create mode 100644 packages/react/src/types/index.ts create mode 100644 packages/react/src/utils/profile.ts diff --git a/packages/react/src/contexts/talentLayerContext.tsx b/packages/react/src/contexts/talentLayerContext.tsx new file mode 100644 index 0000000..a702d9c --- /dev/null +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import { createContext, ReactNode, useEffect, useMemo, useState } from "react"; +import { useAccount } from "wagmi"; +import { IAccount, ICompletionScores, IUser } from "../types"; +import { getPlatform } from "../temp"; +import { getCompletionScores } from "../utils/profile"; +import { TalentLayerClient } from "../../../client/src/index"; +import { TalentLayerClientConfig } from "../../../client/src/types"; + +interface TalentLayerProviderProps { + children: ReactNode; + config: TalentLayerClientConfig & { + platformId: string; + isActiveDelegate?: boolean; + delegateAddress?: string; + }; +} + +const TalentLayerContext = createContext<{ + user?: IUser; + account?: IAccount; + isActiveDelegate: boolean; + refreshData: () => Promise; + loading: boolean; + completionScores?: ICompletionScores; +}>({ + user: undefined, + account: undefined, + isActiveDelegate: false, + refreshData: async () => { + return false; + }, + loading: true, + completionScores: undefined, +}); + +export function TalentLayerProvider(props: TalentLayerProviderProps) { + const { children, config } = props; + + const account = useAccount(); + + const [user, setUser] = useState(); + const [isActiveDelegate, setIsActiveDelegate] = useState(false); + const [loading, setLoading] = useState(true); + const [completionScores, setCompletionScores] = useState(); + + const tlClient = new TalentLayerClient(config); + + // TODO - automatically switch to the default chain is the current one is not part of the config + + async function fetchData() { + if (!account.address || !account.isConnected) { + setLoading(false); + return false; + } + + try { + const userResponse = await tlClient.profile.getByAddress(account.address); + + if (userResponse?.data?.data?.users?.length == 0) { + setLoading(false); + return false; + } + + const currentUser = userResponse.data.data.users[0]; + + const platformResponse = await getPlatform(tlClient, config.platformId); + + const platform = platformResponse?.data?.data?.platform; + currentUser.isAdmin = platform?.address === currentUser?.address; + + setUser(currentUser); + setIsActiveDelegate( + config.isActiveDelegate && + config.delegateAddress && + userResponse.data.data.users[0].delegates && + userResponse.data.data.users[0].delegates.indexOf( + config.delegateAddress.toLowerCase() + ) !== -1 + ); + + setLoading(false); + return true; + } catch (err: any) { + setLoading(false); + console.error(err); + //TODO - Handle error for the developer in a visual manner + return false; + } + } + + useEffect(() => { + fetchData(); + }, [account.address]); + + useEffect(() => { + if (!user) return; + const completionScores = getCompletionScores(user); + setCompletionScores(completionScores); + }, [user]); + + const value = useMemo(() => { + return { + user, + account: account ? account : undefined, + isActiveDelegate, + refreshData: fetchData, + loading, + completionScores, + }; + }, [account.address, user?.id, isActiveDelegate, loading, completionScores]); + + return ( + + {children} + + ); +} + +export default TalentLayerContext; diff --git a/packages/react/src/hooks/useTalentLayer.tsx b/packages/react/src/hooks/useTalentLayer.tsx new file mode 100644 index 0000000..b6c2982 --- /dev/null +++ b/packages/react/src/hooks/useTalentLayer.tsx @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import TalentLayerContext from "../contexts/talentLayerContext"; + +export function useTalentLayer() { + return useContext(TalentLayerContext); +} diff --git a/packages/react/src/temp.ts b/packages/react/src/temp.ts new file mode 100644 index 0000000..f8a2116 --- /dev/null +++ b/packages/react/src/temp.ts @@ -0,0 +1,48 @@ +import { TalentLayerClient } from "../../client/src"; + +const platformFields = ` + id + address + name + createdAt + updatedAt + feePayments + totalPlatformGains + feeClaims + originServiceFeeRate + originValidatedProposalFeeRate + servicePostingFee + proposalPostingFee + arbitrator + arbitratorExtraData + arbitrationFeeTimeout + cid + description + signer +`; + +const platformDescriptionFields = ` + id + about + website + platform + video_url + image_url +`; + +export function getPlatform( + tlClient: TalentLayerClient, + id: string +): Promise { + const query = ` + { + platform(id: ${id}) { + ${platformFields} + description { + ${platformDescriptionFields} + } + } + } + `; + return tlClient.graphQlClient.getFromSubgraph(query); +} diff --git a/packages/react/src/types/index.ts b/packages/react/src/types/index.ts new file mode 100644 index 0000000..79819b3 --- /dev/null +++ b/packages/react/src/types/index.ts @@ -0,0 +1,316 @@ +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 = { + title: string; + name: string; + role?: string; + image_url?: string; + video_url?: string; + about?: string; + skills_raw?: string; + 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; + 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; +}; diff --git a/packages/react/src/utils/profile.ts b/packages/react/src/utils/profile.ts new file mode 100644 index 0000000..361ea68 --- /dev/null +++ b/packages/react/src/utils/profile.ts @@ -0,0 +1,85 @@ +import { + ICompletionScores, + IUser, + IUserDetails, + IWeb3mailPreferences, +} from "../types"; + +const pointsByUserFields = { + handle: 10, +}; + +const pointsByUserDetailsFields = { + title: 10, + name: 10, + role: 10, + image_url: 10, + about: 20, + skills_raw: 20, +}; + +const pointsByWeb3mailFields = { + activeOnNewProposal: 50, +}; + +export function getCompletionScores(user: IUser): ICompletionScores { + let score = 0; + let totalPoint = 0; + + Object.entries(pointsByUserFields).forEach((pointsByField) => { + const [key, value] = pointsByField; + totalPoint += value; + if (user[key as keyof IUser]) { + score += value; + } + }); + + let userDetailsScore = 0; + let userDetailsTotalPoint = 0; + Object.entries(pointsByUserDetailsFields).forEach((pointsByField) => { + const [key, value] = pointsByField; + totalPoint += value; + userDetailsTotalPoint += value; + if (user.description && user.description[key as keyof IUserDetails]) { + score += value; + userDetailsScore += value; + } + }); + + let userWeb3mailScore = 0; + let userWeb3mailTotalPoint = 0; + Object.entries(pointsByWeb3mailFields).forEach((pointsByField) => { + const [key, value] = pointsByField; + totalPoint += value; + userWeb3mailTotalPoint += value; + if ( + user.description?.web3mailPreferences && + user.description.web3mailPreferences[ + key as keyof IWeb3mailPreferences + ] !== null + ) { + score += value; + userWeb3mailScore += value; + } + }); + + return { + total: { + score: score, + totalPoint: totalPoint, + percentage: Math.round((score * 100) / totalPoint), + }, + userDetails: { + percentage: Math.round((userDetailsScore * 100) / userDetailsTotalPoint), + score: userDetailsScore, + totalPoint: userDetailsTotalPoint, + }, + web3mail: { + percentage: Math.round( + (userWeb3mailScore * 100) / userWeb3mailTotalPoint + ), + score: userWeb3mailScore, + totalPoint: userWeb3mailTotalPoint, + }, + }; +} From 602f4a7a7fed34e27ba4eb57bddf68d4a955e6c5 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Wed, 27 Sep 2023 22:41:43 +0530 Subject: [PATCH 03/10] added exports via index.ts --- packages/react/src/hooks/useTalentLayer.tsx | 2 +- packages/react/src/index.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react/src/hooks/useTalentLayer.tsx b/packages/react/src/hooks/useTalentLayer.tsx index b6c2982..abb5f80 100644 --- a/packages/react/src/hooks/useTalentLayer.tsx +++ b/packages/react/src/hooks/useTalentLayer.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; import TalentLayerContext from "../contexts/talentLayerContext"; -export function useTalentLayer() { +export default function useTalentLayer() { return useContext(TalentLayerContext); } diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index e69de29..6db7994 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -0,0 +1,8 @@ +import TalentLayerContext from "./contexts/talentLayerContext"; +import { TalentLayerProvider } from "./contexts/talentLayerContext"; +import useTalentLayer from "./hooks/useTalentLayer"; + +export default { + context: { TalentLayerContext, TalentLayerProvider }, + hooks: { useTalentLayer }, +}; From 2fb5b4024c91e4c3760e7b156acc310b85080bf7 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Wed, 4 Oct 2023 12:46:56 +0530 Subject: [PATCH 04/10] React SDK - initial hooks and context --- packages/react/package.json | 2 + .../react/src/components/TalentLayerError.tsx | 22 +++ .../react/src/contexts/talentLayerContext.tsx | 99 ++++++----- packages/react/src/hooks/useFees.ts | 55 ++++++ packages/react/src/hooks/useMintFee.ts | 46 +++++ .../react/src/hooks/usePaymentsByService.ts | 34 ++++ .../react/src/hooks/usePaymentsForUser.ts | 71 ++++++++ packages/react/src/hooks/usePlatform.ts | 37 ++++ packages/react/src/hooks/useProposal.ts | 59 +++++++ packages/react/src/hooks/useServices.ts | 69 ++++++++ .../{useTalentLayer.tsx => useTalentLayer.ts} | 0 packages/react/src/hooks/useUser.ts | 61 +++++++ packages/react/src/index.ts | 18 +- packages/react/src/queries/fees.ts | 20 +++ packages/react/src/queries/index.ts | 9 + packages/react/src/queries/payments.ts | 64 +++++++ .../src/{temp.ts => queries/platform.ts} | 23 ++- packages/react/src/queries/proposals.ts | 114 +++++++++++++ packages/react/src/queries/reviews.ts | 24 +++ packages/react/src/queries/users.ts | 158 ++++++++++++++++++ packages/react/src/types/index.ts | 35 ++-- packages/react/src/utils/index.ts | 0 packages/react/src/utils/profile.ts | 85 ---------- 23 files changed, 942 insertions(+), 163 deletions(-) create mode 100644 packages/react/src/components/TalentLayerError.tsx create mode 100644 packages/react/src/hooks/useFees.ts create mode 100644 packages/react/src/hooks/useMintFee.ts create mode 100644 packages/react/src/hooks/usePaymentsByService.ts create mode 100644 packages/react/src/hooks/usePaymentsForUser.ts create mode 100644 packages/react/src/hooks/usePlatform.ts create mode 100644 packages/react/src/hooks/useProposal.ts create mode 100644 packages/react/src/hooks/useServices.ts rename packages/react/src/hooks/{useTalentLayer.tsx => useTalentLayer.ts} (100%) create mode 100644 packages/react/src/hooks/useUser.ts create mode 100644 packages/react/src/queries/fees.ts create mode 100644 packages/react/src/queries/index.ts create mode 100644 packages/react/src/queries/payments.ts rename packages/react/src/{temp.ts => queries/platform.ts} (65%) create mode 100644 packages/react/src/queries/proposals.ts create mode 100644 packages/react/src/queries/reviews.ts create mode 100644 packages/react/src/queries/users.ts create mode 100644 packages/react/src/utils/index.ts delete mode 100644 packages/react/src/utils/profile.ts diff --git a/packages/react/package.json b/packages/react/package.json index dcb1f71..1557708 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -20,6 +20,8 @@ }, "dependencies": { "axios": "^1.5.0", + "ipfs-http-client": "^60.0.1", + "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 index a702d9c..e8e4d4b 100644 --- a/packages/react/src/contexts/talentLayerContext.tsx +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -1,83 +1,84 @@ -import React from "react"; -import { createContext, ReactNode, useEffect, useMemo, useState } from "react"; -import { useAccount } from "wagmi"; -import { IAccount, ICompletionScores, IUser } from "../types"; -import { getPlatform } from "../temp"; -import { getCompletionScores } from "../utils/profile"; -import { TalentLayerClient } from "../../../client/src/index"; -import { TalentLayerClientConfig } from "../../../client/src/types"; +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'; + +type TalentLayerConfig = { + chainId: NetworkEnum; + platformId: number; + infuraClientId: string; + infuraClientSecret: string; +}; interface TalentLayerProviderProps { children: ReactNode; - config: TalentLayerClientConfig & { - platformId: string; - isActiveDelegate?: boolean; - delegateAddress?: string; - }; + config: TalentLayerConfig; +} + +export interface Subgraph { + query: (query: string) => any; } const TalentLayerContext = createContext<{ user?: IUser; account?: IAccount; - isActiveDelegate: boolean; refreshData: () => Promise; loading: boolean; - completionScores?: ICompletionScores; + client: TalentLayerClient | undefined; + platformId: number; + chainId?: NetworkEnum; + subgraph: Subgraph; }>({ user: undefined, account: undefined, - isActiveDelegate: false, refreshData: async () => { return false; }, loading: true, - completionScores: undefined, + 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 = useAccount(); const [user, setUser] = useState(); - const [isActiveDelegate, setIsActiveDelegate] = useState(false); const [loading, setLoading] = useState(true); - const [completionScores, setCompletionScores] = useState(); - const tlClient = new TalentLayerClient(config); + let talentLayerClient = new TalentLayerClient(config); - // TODO - automatically switch to the default chain is the current one is not part of the config + async function loadData() { + if (!talentLayerClient) return false; - async function fetchData() { if (!account.address || !account.isConnected) { setLoading(false); return false; } try { - const userResponse = await tlClient.profile.getByAddress(account.address); + const userResponse = await talentLayerClient.profile.getByAddress(account.address); - if (userResponse?.data?.data?.users?.length == 0) { + if (userResponse) { setLoading(false); return false; } - const currentUser = userResponse.data.data.users[0]; + const currentUser = userResponse; - const platformResponse = await getPlatform(tlClient, config.platformId); + const platformResponse = await talentLayerClient.platform.getOne( + config.platformId.toString(), + ); - const platform = platformResponse?.data?.data?.platform; + const platform = platformResponse; currentUser.isAdmin = platform?.address === currentUser?.address; setUser(currentUser); - setIsActiveDelegate( - config.isActiveDelegate && - config.delegateAddress && - userResponse.data.data.users[0].delegates && - userResponse.data.data.users[0].delegates.indexOf( - config.delegateAddress.toLowerCase() - ) !== -1 - ); setLoading(false); return true; @@ -90,31 +91,25 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { } useEffect(() => { - fetchData(); - }, [account.address]); + if (!talentLayerClient) return; - useEffect(() => { - if (!user) return; - const completionScores = getCompletionScores(user); - setCompletionScores(completionScores); - }, [user]); + loadData(); + }, [account.address]); - const value = useMemo(() => { + const value = useMemo>(() => { return { user, account: account ? account : undefined, - isActiveDelegate, - refreshData: fetchData, + refreshData: loadData, loading, - completionScores, + client: talentLayerClient, + chainId, + platformId, + subgraph: { query: (query: string) => talentLayerClient.graphQlClient.get(query) }, }; - }, [account.address, user?.id, isActiveDelegate, loading, completionScores]); + }, [account.address, user?.id, loading]); - return ( - - {children} - - ); + 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..92430e7 --- /dev/null +++ b/packages/react/src/hooks/useFees.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { IFees } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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 query = queries.fees.getProtocolAndPlatformsFees( + originServicePlatformId, + originValidatedProposalPlatformId, + ); + const response = await talentLayer.subgraph.query(query); + const data = response.data; + + if (data) { + fees.protocolEscrowFeeRate = data.protocols[0].protocolEscrowFeeRate; + fees.originServiceFeeRate = data.servicePlatform.originServiceFeeRate; + fees.originValidatedProposalFeeRate = data.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..9b2ba70 --- /dev/null +++ b/packages/react/src/hooks/useMintFee.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; +import useTalentLayer from './useTalentLayer'; + +const query = ` +{ + protocols { + userMintFee, + shortHandlesMaxPrice + } +} + `; + +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.subgraph.query(query); + const data = response.data; + + if (data) { + const protocol = data.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..699b519 --- /dev/null +++ b/packages/react/src/hooks/usePaymentsByService.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react'; +import { IPayment, PaymentTypeEnum } from '../types'; +import queries from '../queries'; +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 query = queries.payments.getPaymentsByService(id, paymentType); + const response = await talentLayer.subgraph.query(query); + + if (response?.data?.data?.payments) { + setPayments(response.data.data.payments); + } + } 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..c551328 --- /dev/null +++ b/packages/react/src/hooks/usePaymentsForUser.ts @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import { IPayment } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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() { + setLoading(true); + try { + const query = queries.payments.getPaymentsForUser( + id, + total, + 0, + start.toString(), + end.toString(), + ); + const response = talentLayer.subgraph.query(query); + + if (response && response.data && response.data.data) { + setPayments([...response.data.data.payments]); + + if (response.data.data.payments.length < total) { + setHasMoreData(false); + } + } + } catch (error: any) { + console.error(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..e0729f3 --- /dev/null +++ b/packages/react/src/hooks/useProposal.ts @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react'; +import { IProposal, OnlyOne } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +export default function useProposal( + filter: + | OnlyOne<{ + proposalId?: string; + serviceId?: string; + userId?: string; + }> + | undefined, +) { + 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.proposalId !== undefined) { + const proposals = await talentLayer.client.proposal.getOne(filter.proposalId); + return setData(proposals as IProposal[]); + } + + if (filter.serviceId !== undefined) { + const query = queries.proposals.getAllProposalsByServiceId(filter.serviceId); + const proposals = await talentLayer.subgraph.query(query); + return setData(proposals.data.proposals as IProposal[]); + } + + if (filter.userId !== undefined) { + const query = queries.proposals.getAllProposalsByUser(filter.userId); + const proposals = await talentLayer.subgraph.query(query); + 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/useServices.ts b/packages/react/src/hooks/useServices.ts new file mode 100644 index 0000000..d52cd29 --- /dev/null +++ b/packages/react/src/hooks/useServices.ts @@ -0,0 +1,69 @@ +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.search({ + 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.tsx b/packages/react/src/hooks/useTalentLayer.ts similarity index 100% rename from packages/react/src/hooks/useTalentLayer.tsx rename to packages/react/src/hooks/useTalentLayer.ts diff --git a/packages/react/src/hooks/useUser.ts b/packages/react/src/hooks/useUser.ts new file mode 100644 index 0000000..1ba05a8 --- /dev/null +++ b/packages/react/src/hooks/useUser.ts @@ -0,0 +1,61 @@ +import { useEffect, useState } from 'react'; +import { getUsers } from '../queries/users'; +import { IUser } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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 query = queries.users.getUsers(options.numberPerPage, offset, options.searchQuery); + const response = await talentLayer.subgraph.query(query); + + 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 index 6db7994..80190ed 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,8 +1,12 @@ -import TalentLayerContext from "./contexts/talentLayerContext"; -import { TalentLayerProvider } from "./contexts/talentLayerContext"; -import useTalentLayer from "./hooks/useTalentLayer"; +import { TalentLayerProvider } from './contexts/talentLayerContext'; +import useTalentLayer from './hooks/useTalentLayer'; +import useServices from './hooks/useServices'; +import usePlatform from './hooks/usePlatform'; +import useProposal from './hooks/useProposal' +import useFees from './hooks/useFees'; +import useMintFee from './hooks/useMintFee'; +import useUsers from './hooks/useUser'; +import usePaymentsByService from './hooks/usePaymentsByService'; +import usePaymentsForUser from './hooks/usePaymentsForUser'; -export default { - context: { TalentLayerContext, TalentLayerProvider }, - hooks: { useTalentLayer }, -}; +export { TalentLayerProvider, useTalentLayer, useServices, usePlatform, useProposal, useFees, useMintFee, useUsers, usePaymentsByService, usePaymentsForUser }; diff --git a/packages/react/src/queries/fees.ts b/packages/react/src/queries/fees.ts new file mode 100644 index 0000000..69e70b8 --- /dev/null +++ b/packages/react/src/queries/fees.ts @@ -0,0 +1,20 @@ +export function getProtocolAndPlatformsFees( + originServicePlatformId: string, + originValidatedProposalPlatformId: string, +) { + const query = ` + { + protocols { + protocolEscrowFeeRate + } + servicePlatform: platform(id:${originServicePlatformId}){ + originServiceFeeRate + } + proposalPlatform: platform(id:${originValidatedProposalPlatformId}){ + originValidatedProposalFeeRate + } + } + `; + + return query; +} diff --git a/packages/react/src/queries/index.ts b/packages/react/src/queries/index.ts new file mode 100644 index 0000000..7832644 --- /dev/null +++ b/packages/react/src/queries/index.ts @@ -0,0 +1,9 @@ +import * as fees from './fees'; +import * as payments from './payments'; +import * as platform from './platform'; +import * as proposals from './proposals'; +import * as reviews from './reviews'; +import * as users from './users'; + +const queries = { fees, payments, platform, proposals, reviews, users }; +export default queries \ No newline at end of file diff --git a/packages/react/src/queries/payments.ts b/packages/react/src/queries/payments.ts new file mode 100644 index 0000000..76f39b8 --- /dev/null +++ b/packages/react/src/queries/payments.ts @@ -0,0 +1,64 @@ +export function getPaymentsByService(serviceId: string, paymentType?: string) { + let condition = `where: {service: "${serviceId}"`; + paymentType ? (condition += `, paymentType: "${paymentType}"`) : ''; + condition += '}, orderBy: id, orderDirection: asc'; + const query = ` + { + payments(${condition}) { + id + amount + rateToken { + address + decimals + name + symbol + } + paymentType + transactionHash + createdAt + } + } + `; + return query; +} + +export function getPaymentsForUser( + userId: string, + numberPerPage?: number, + offset?: number, + startDate?: string, + endDate?: string, +) { + const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : ''; + + const startDataCondition = startDate ? `, createdAt_gte: "${startDate}"` : ''; + const endDateCondition = endDate ? `, createdAt_lte: "${endDate}"` : ''; + + const query = ` + { + payments(where: { + service_: {seller: "${userId}"} + ${startDataCondition} + ${endDateCondition} + }, + orderBy: createdAt orderDirection: desc ${pagination} ) { + id, + rateToken { + address + decimals + name + symbol + } + amount + transactionHash + paymentType + createdAt + service { + id, + cid + } + } + } + `; + return query; +} diff --git a/packages/react/src/temp.ts b/packages/react/src/queries/platform.ts similarity index 65% rename from packages/react/src/temp.ts rename to packages/react/src/queries/platform.ts index f8a2116..4455c8c 100644 --- a/packages/react/src/temp.ts +++ b/packages/react/src/queries/platform.ts @@ -1,5 +1,3 @@ -import { TalentLayerClient } from "../../client/src"; - const platformFields = ` id address @@ -30,10 +28,7 @@ const platformDescriptionFields = ` image_url `; -export function getPlatform( - tlClient: TalentLayerClient, - id: string -): Promise { +export function getPlatform(id: string) { const query = ` { platform(id: ${id}) { @@ -44,5 +39,19 @@ export function getPlatform( } } `; - return tlClient.graphQlClient.getFromSubgraph(query); + return query; +} + +export function getPlatformsByOwner(addressOwner: string) { + const query = ` + { + platforms(where: {address: "${addressOwner}"}) { + ${platformFields} + description { + ${platformDescriptionFields} + } + } + } + `; + return query; } diff --git a/packages/react/src/queries/proposals.ts b/packages/react/src/queries/proposals.ts new file mode 100644 index 0000000..7f33af8 --- /dev/null +++ b/packages/react/src/queries/proposals.ts @@ -0,0 +1,114 @@ +export function getAllProposalsByServiceId(id: string) { + const query = ` + { + proposals(where: {service_: {id: "${id}"}}) { + service { + id, + cid + buyer { + id + } + platform { + id + } + } + cid + id + status + rateToken { + address + decimals + name + symbol + } + rateAmount + createdAt + updatedAt + seller { + id + handle + address + cid + rating + userStats { + numReceivedReviews + } + } + description { + id + about + expectedHours + startDate + video_url + } + expirationDate + platform { + id + } + } + } + `; + return query; +} + +export function getAllProposalsByUser(id: string) { + const query = ` + { + proposals(where: {seller: "${id}", status: "Pending"}) { + id + rateAmount + rateToken { + address + decimals + name + symbol + } + status + cid + createdAt + seller { + id + handle + } + service { + id + cid + createdAt + buyer { + id + handle + } + } + description { + id + about + expectedHours + startDate + video_url + } + expirationDate + } + } + `; + return query; +} + +export function getProposalById(id: string) { + const query = ` + { + proposals(where: {id: "${id}"}) { + rateToken { + address + } + rateAmount + description { + about + video_url + } + status + expirationDate + } + } + `; + return query; +} diff --git a/packages/react/src/queries/reviews.ts b/packages/react/src/queries/reviews.ts new file mode 100644 index 0000000..5c00591 --- /dev/null +++ b/packages/react/src/queries/reviews.ts @@ -0,0 +1,24 @@ +export function getReviewsByService(serviceId: string) { + const query = ` + { + reviews(where: { service: "${serviceId}" }, orderBy: id, orderDirection: desc) { + id + rating + createdAt + service { + id + status + } + to { + id + handle + } + description{ + id + content + } + } + } + `; + return query; +} diff --git a/packages/react/src/queries/users.ts b/packages/react/src/queries/users.ts new file mode 100644 index 0000000..1f2dd5e --- /dev/null +++ b/packages/react/src/queries/users.ts @@ -0,0 +1,158 @@ +export function getUsers(numberPerPage?: number, offset?: number, searchQuery?: string) { + const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : ''; + let condition = ', where: {'; + condition += searchQuery ? `, handle_contains_nocase: "${searchQuery}"` : ''; + condition += '}'; + + const query = ` + { + users(orderBy: rating, orderDirection: desc ${pagination} ${condition}) { + id + address + handle + userStats { + numReceivedReviews + } + rating + } + } + `; + return query; +} + +export function getUserById(id: string) { + const query = ` + { + user(id: "${id}") { + id + address + handle + rating + delegates + userStats { + numReceivedReviews + } + updatedAt + createdAt + description { + about + role + name + country + headline + id + image_url + video_url + title + timezone + skills_raw + web3mailPreferences{ + activeOnNewService + activeOnNewProposal + activeOnProposalValidated + activeOnFundRelease + activeOnReview + activeOnPlatformMarketing + activeOnProtocolMarketing + } + } + } + } + `; + return query; +} + +export function getUserByAddress(address: string) { + const query = ` + { + users(where: {address: "${address.toLocaleLowerCase()}"}, first: 1) { + id + address + handle + rating + delegates + userStats { + numReceivedReviews + } + updatedAt + createdAt + description { + about + role + name + country + headline + id + image_url + video_url + title + timezone + skills_raw + web3mailPreferences{ + activeOnNewService + activeOnNewProposal + activeOnProposalValidated + activeOnFundRelease + activeOnReview + activeOnPlatformMarketing + activeOnProtocolMarketing + } + } + } + } + `; + return query; +} + +export function getUserTotalGains(id: string) { + const query = ` + { + user(id: "${id}") { + totalGains{ + id + totalGain + token { + id + name + symbol + decimals + } + } + } + } + `; + return query; +} + +export function getUserByIds(ids: string[]) { + const query = ` + { + users(where: {id_in: [${ids.join(',')}]}) { + id + address + handle + rating + delegates + userStats { + numReceivedReviews + } + updatedAt + createdAt + description { + about + role + name + country + headline + id + image_url + video_url + title + timezone + skills_raw + } + } + } + `; + return query; +} diff --git a/packages/react/src/types/index.ts b/packages/react/src/types/index.ts index 79819b3..9d6c1f0 100644 --- a/packages/react/src/types/index.ts +++ b/packages/react/src/types/index.ts @@ -1,4 +1,4 @@ -import { Connector } from "wagmi"; +import { Connector } from 'wagmi'; export type IHive = { id: string; @@ -65,7 +65,7 @@ export type IAccount = { isReconnecting: boolean; isConnected: boolean; isDisconnected: boolean; - status: "connecting" | "reconnecting" | "connected" | "disconnected"; + status: 'connecting' | 'reconnecting' | 'connected' | 'disconnected'; }; export type ICompletionScores = { @@ -217,17 +217,17 @@ export type IReviewDetails = { }; export enum ServiceStatusEnum { - Opened = "Opened", - Confirmed = "Confirmed", - Finished = "Finished", - Cancelled = "Cancelled", - Uncompleted = "Uncompleted", + Opened = 'Opened', + Confirmed = 'Confirmed', + Finished = 'Finished', + Cancelled = 'Cancelled', + Uncompleted = 'Uncompleted', } export enum ProposalStatusEnum { - Pending = "Pending", - Validated = "Validated", - Rejected = "Rejected", + Pending = 'Pending', + Validated = 'Validated', + Rejected = 'Rejected', } export type IProposalDetails = { @@ -275,8 +275,8 @@ export enum ProfileTypeEnum { } export enum PaymentTypeEnum { - Release = "Release", - Reimburse = "Reimburse", + Release = 'Release', + Reimburse = 'Reimburse', } export enum NetworkEnum { @@ -314,3 +314,14 @@ export type IUserGain = { 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/src/utils/profile.ts b/packages/react/src/utils/profile.ts deleted file mode 100644 index 361ea68..0000000 --- a/packages/react/src/utils/profile.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - ICompletionScores, - IUser, - IUserDetails, - IWeb3mailPreferences, -} from "../types"; - -const pointsByUserFields = { - handle: 10, -}; - -const pointsByUserDetailsFields = { - title: 10, - name: 10, - role: 10, - image_url: 10, - about: 20, - skills_raw: 20, -}; - -const pointsByWeb3mailFields = { - activeOnNewProposal: 50, -}; - -export function getCompletionScores(user: IUser): ICompletionScores { - let score = 0; - let totalPoint = 0; - - Object.entries(pointsByUserFields).forEach((pointsByField) => { - const [key, value] = pointsByField; - totalPoint += value; - if (user[key as keyof IUser]) { - score += value; - } - }); - - let userDetailsScore = 0; - let userDetailsTotalPoint = 0; - Object.entries(pointsByUserDetailsFields).forEach((pointsByField) => { - const [key, value] = pointsByField; - totalPoint += value; - userDetailsTotalPoint += value; - if (user.description && user.description[key as keyof IUserDetails]) { - score += value; - userDetailsScore += value; - } - }); - - let userWeb3mailScore = 0; - let userWeb3mailTotalPoint = 0; - Object.entries(pointsByWeb3mailFields).forEach((pointsByField) => { - const [key, value] = pointsByField; - totalPoint += value; - userWeb3mailTotalPoint += value; - if ( - user.description?.web3mailPreferences && - user.description.web3mailPreferences[ - key as keyof IWeb3mailPreferences - ] !== null - ) { - score += value; - userWeb3mailScore += value; - } - }); - - return { - total: { - score: score, - totalPoint: totalPoint, - percentage: Math.round((score * 100) / totalPoint), - }, - userDetails: { - percentage: Math.round((userDetailsScore * 100) / userDetailsTotalPoint), - score: userDetailsScore, - totalPoint: userDetailsTotalPoint, - }, - web3mail: { - percentage: Math.round( - (userWeb3mailScore * 100) / userWeb3mailTotalPoint - ), - score: userWeb3mailScore, - totalPoint: userWeb3mailTotalPoint, - }, - }; -} From 51b903fb5d29f0a33157ee64fb3896afd2e52c1b Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Sat, 21 Oct 2023 15:18:02 +0530 Subject: [PATCH 05/10] React SDK changes --- packages/react/package.json | 1 + .../react/src/contexts/talentLayerContext.tsx | 26 +++----- packages/react/src/hooks/useProposal.ts | 37 +++-------- packages/react/src/hooks/useProposals.ts | 51 ++++++++++++++++ packages/react/src/hooks/useReviews.tsx | 37 +++++++++++ packages/react/src/hooks/useService.ts | 32 ++++++++++ packages/react/src/hooks/useServices.ts | 3 +- packages/react/src/hooks/useUser.ts | 56 ++++++----------- packages/react/src/hooks/useUsers.ts | 61 +++++++++++++++++++ packages/react/src/index.ts | 27 ++++++-- packages/react/src/types/index.ts | 4 +- packages/react/tsconfig.json | 1 + 12 files changed, 246 insertions(+), 90 deletions(-) create mode 100644 packages/react/src/hooks/useProposals.ts create mode 100644 packages/react/src/hooks/useReviews.tsx create mode 100644 packages/react/src/hooks/useService.ts create mode 100644 packages/react/src/hooks/useUsers.ts diff --git a/packages/react/package.json b/packages/react/package.json index 1557708..bed9f56 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -19,6 +19,7 @@ "typescript": "^5.2.2" }, "dependencies": { + "@talentlayer/client": "^0.1.6", "axios": "^1.5.0", "ipfs-http-client": "^60.0.1", "net": "^1.0.2", diff --git a/packages/react/src/contexts/talentLayerContext.tsx b/packages/react/src/contexts/talentLayerContext.tsx index e8e4d4b..fb705f7 100644 --- a/packages/react/src/contexts/talentLayerContext.tsx +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -1,19 +1,16 @@ +'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'; -type TalentLayerConfig = { - chainId: NetworkEnum; - platformId: number; - infuraClientId: string; - infuraClientSecret: string; -}; - interface TalentLayerProviderProps { children: ReactNode; - config: TalentLayerConfig; + config: ConstructorParameters[0] & { + account: ReturnType; + }; } export interface Subgraph { @@ -46,7 +43,7 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { const { children, config } = props; const { chainId, platformId } = config; - const account = useAccount(); + const account = config.account; const [user, setUser] = useState(); const [loading, setLoading] = useState(true); @@ -64,11 +61,6 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { try { const userResponse = await talentLayerClient.profile.getByAddress(account.address); - if (userResponse) { - setLoading(false); - return false; - } - const currentUser = userResponse; const platformResponse = await talentLayerClient.platform.getOne( @@ -80,13 +72,13 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { setUser(currentUser); - setLoading(false); return true; } catch (err: any) { - setLoading(false); console.error(err); //TODO - Handle error for the developer in a visual manner return false; + } finally { + setLoading(false); } } @@ -106,7 +98,7 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { chainId, platformId, subgraph: { query: (query: string) => talentLayerClient.graphQlClient.get(query) }, - }; + } as any; }, [account.address, user?.id, loading]); return {children}; diff --git a/packages/react/src/hooks/useProposal.ts b/packages/react/src/hooks/useProposal.ts index e0729f3..c5c371d 100644 --- a/packages/react/src/hooks/useProposal.ts +++ b/packages/react/src/hooks/useProposal.ts @@ -1,18 +1,9 @@ import { useEffect, useState } from 'react'; -import { IProposal, OnlyOne } from '../types'; +import { IProposal } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; -export default function useProposal( - filter: - | OnlyOne<{ - proposalId?: string; - serviceId?: string; - userId?: string; - }> - | undefined, -) { - const [data, setData] = useState(); +export default function useProposal(proposalId: string) { + const [data, setData] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -23,23 +14,9 @@ export default function useProposal( if (!talentLayer.client) return; try { - if (filter === undefined) return setData(undefined); - - if (filter.proposalId !== undefined) { - const proposals = await talentLayer.client.proposal.getOne(filter.proposalId); - return setData(proposals as IProposal[]); - } - - if (filter.serviceId !== undefined) { - const query = queries.proposals.getAllProposalsByServiceId(filter.serviceId); - const proposals = await talentLayer.subgraph.query(query); - return setData(proposals.data.proposals as IProposal[]); - } - - if (filter.userId !== undefined) { - const query = queries.proposals.getAllProposalsByUser(filter.userId); - const proposals = await talentLayer.subgraph.query(query); - return setData(proposals.data.proposals as IProposal[]); + if (proposalId !== undefined) { + const proposal = await talentLayer.client.proposal.getOne(proposalId); + return setData(proposal); } throw new Error('Proposal not found'); @@ -53,7 +30,7 @@ export default function useProposal( 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..e70c191 --- /dev/null +++ b/packages/react/src/hooks/useProposals.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; +import { IProposal, OnlyOne } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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 query = queries.proposals.getAllProposalsByServiceId(filter.serviceId); + const proposals = await talentLayer.subgraph.query(query); + return setData(proposals.data.proposals as IProposal[]); + } + + if (filter.userId !== undefined) { + const query = queries.proposals.getAllProposalsByUser(filter.userId); + const proposals = await talentLayer.subgraph.query(query); + 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.tsx b/packages/react/src/hooks/useReviews.tsx new file mode 100644 index 0000000..50a8232 --- /dev/null +++ b/packages/react/src/hooks/useReviews.tsx @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react'; +import { IProposal, IReview } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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 query = queries.reviews.getReviewsByService(serviceId); + const reviews = await talentLayer.subgraph.query(query); + console.log(reviews.data) + return setData(reviews.data); + + 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/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 index d52cd29..715d5a2 100644 --- a/packages/react/src/hooks/useServices.ts +++ b/packages/react/src/hooks/useServices.ts @@ -26,11 +26,12 @@ export default function useServices(filters: { let response; let newServices: IService[] = []; - response = await talentLayer.client.service.search({ + response = await talentLayer.client.service.getServices({ offset, ...filters, platformId: filters.platformId || talentLayer.platformId.toString(), }); + if (filters.searchQuery) { newServices = response.data; } else { diff --git a/packages/react/src/hooks/useUser.ts b/packages/react/src/hooks/useUser.ts index 1ba05a8..5eeb716 100644 --- a/packages/react/src/hooks/useUser.ts +++ b/packages/react/src/hooks/useUser.ts @@ -1,16 +1,10 @@ import { useEffect, useState } from 'react'; -import { getUsers } from '../queries/users'; -import { IUser } from '../types'; +import { IUser, OnlyOne } from '../types'; import useTalentLayer from './useTalentLayer'; import queries from '../queries'; -export default function useUsers (options: { - searchQuery?: string; - numberPerPage?: number; -}) { - const [users, setUsers] = useState([]); - const [hasMoreData, setHasMoreData] = useState(true); - const [offset, setOffset] = useState(0); +export default function useUser(options: OnlyOne<{ userId: string; address: string }>) { + const [user, setUser] = useState(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -20,42 +14,30 @@ export default function useUsers (options: { if (!talentLayer.client) return; try { - setLoading(true); - const query = queries.users.getUsers(options.numberPerPage, offset, options.searchQuery); - const response = await talentLayer.subgraph.query(query); - - 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); + if (options.userId) { + const query = queries.users.getUserById(options.userId); + const response = await talentLayer.subgraph.query(query); + setUser(response.data?.user); + } + + if (options.address) { + const query = queries.users.getUserByAddress(options.address); + const response = await talentLayer.subgraph.query(query); + setUser(response.data?.user); } + + setLoading(true); } catch (error: any) { console.error(error); - setError(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; -}; + }, [options]); + 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..1ba05a8 --- /dev/null +++ b/packages/react/src/hooks/useUsers.ts @@ -0,0 +1,61 @@ +import { useEffect, useState } from 'react'; +import { getUsers } from '../queries/users'; +import { IUser } from '../types'; +import useTalentLayer from './useTalentLayer'; +import queries from '../queries'; + +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 query = queries.users.getUsers(options.numberPerPage, offset, options.searchQuery); + const response = await talentLayer.subgraph.query(query); + + 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 index 80190ed..056637d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,12 +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 useProposal from './hooks/useProposal' -import useFees from './hooks/useFees'; +import useProposals from './hooks/useProposals'; +import useProposal from './hooks/useProposal'; +import useFees from './hooks/useFees'; import useMintFee from './hooks/useMintFee'; -import useUsers from './hooks/useUser'; +import useUsers from './hooks/useUsers'; +import useUser from './hooks/useUser'; import usePaymentsByService from './hooks/usePaymentsByService'; import usePaymentsForUser from './hooks/usePaymentsForUser'; -export { TalentLayerProvider, useTalentLayer, useServices, usePlatform, useProposal, useFees, useMintFee, useUsers, usePaymentsByService, 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 index 9d6c1f0..3d1a044 100644 --- a/packages/react/src/types/index.ts +++ b/packages/react/src/types/index.ts @@ -30,6 +30,7 @@ export type IUser = { }; export type IUserDetails = { + id: string; title: string; name: string; role?: string; @@ -37,9 +38,9 @@ export type IUserDetails = { video_url?: string; about?: string; skills_raw?: string; + user: IUser; web3mailPreferences?: IWeb3mailPreferences; }; - export type IWeb3mailPreferences = { activeOnNewService: boolean; activeOnNewProposal: boolean; @@ -246,6 +247,7 @@ export type IProposal = { cid: string; status: ProposalStatusEnum; seller: IUser; + buyer: IUser; rateToken: IToken; rateAmount: string; service: IService; diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 22521ef..649ef00 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -5,5 +5,6 @@ "compilerOptions": { "jsx": "react", "outDir": "dist", + "lib": [ "ES2023", "DOM"] } } From a575aa2baab6dae0a896d99949f66eb597e15dd7 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Sat, 21 Oct 2023 15:26:30 +0530 Subject: [PATCH 06/10] change license --- packages/react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/package.json b/packages/react/package.json index bed9f56..50ace17 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -4,7 +4,7 @@ "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": "MIT", + "license": "Apache-2.0", "private": false, "scripts": { "lint": "eslint .", From ad2a6229e4d63f3a4fe9ab3741eeae658804f9e7 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Sat, 21 Oct 2023 15:29:13 +0530 Subject: [PATCH 07/10] remove unused dependancies --- packages/react/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index 50ace17..2a52bf4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -20,8 +20,6 @@ }, "dependencies": { "@talentlayer/client": "^0.1.6", - "axios": "^1.5.0", - "ipfs-http-client": "^60.0.1", "net": "^1.0.2", "react": "^18.2.0", "wagmi": "^1.4.2" From c1a0dd7240a46fa6971e42772bf9d053ed742e31 Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Sat, 21 Oct 2023 16:18:08 +0530 Subject: [PATCH 08/10] renamed tsx hook to .ts --- packages/react/src/hooks/{useReviews.tsx => useReviews.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react/src/hooks/{useReviews.tsx => useReviews.ts} (100%) diff --git a/packages/react/src/hooks/useReviews.tsx b/packages/react/src/hooks/useReviews.ts similarity index 100% rename from packages/react/src/hooks/useReviews.tsx rename to packages/react/src/hooks/useReviews.ts From d26fc36c8562e2becff6ae5c1fab98b02012a3ec Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Mon, 23 Oct 2023 11:00:14 +0530 Subject: [PATCH 09/10] fix reinitialization of client every render --- .../react/src/contexts/talentLayerContext.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react/src/contexts/talentLayerContext.tsx b/packages/react/src/contexts/talentLayerContext.tsx index fb705f7..b36a74c 100644 --- a/packages/react/src/contexts/talentLayerContext.tsx +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -48,7 +48,7 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { const [user, setUser] = useState(); const [loading, setLoading] = useState(true); - let talentLayerClient = new TalentLayerClient(config); + const [talentLayerClient, setTalentLayerClient] = useState(); async function loadData() { if (!talentLayerClient) return false; @@ -75,18 +75,25 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { return true; } catch (err: any) { console.error(err); - //TODO - Handle error for the developer in a visual manner + return false; } finally { setLoading(false); } } + useEffect(() => { + if (chainId && account.address) { + const tlClient = new TalentLayerClient(config); + setTalentLayerClient(tlClient); + } + }, [chainId, account.address]); + useEffect(() => { if (!talentLayerClient) return; loadData(); - }, [account.address]); + }, [talentLayerClient]); const value = useMemo>(() => { return { @@ -97,7 +104,9 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { client: talentLayerClient, chainId, platformId, - subgraph: { query: (query: string) => talentLayerClient.graphQlClient.get(query) }, + subgraph: { + query: (query: string) => (talentLayerClient as TalentLayerClient).graphQlClient.get(query), + }, } as any; }, [account.address, user?.id, loading]); From 27dc211a52094809435b15be374701a1d8aeedfe Mon Sep 17 00:00:00 2001 From: Spandan Barve Date: Sun, 5 Nov 2023 01:52:39 +0530 Subject: [PATCH 10/10] Use new client to remove queries from eact --- packages/react/package.json | 2 +- .../react/src/contexts/talentLayerContext.tsx | 2 +- packages/react/src/hooks/useFees.ts | 18 +- packages/react/src/hooks/useMintFee.ts | 16 +- .../react/src/hooks/usePaymentsByService.ts | 8 +- .../react/src/hooks/usePaymentsForUser.ts | 23 +-- packages/react/src/hooks/useProposals.ts | 22 ++- packages/react/src/hooks/useReviews.ts | 10 +- packages/react/src/hooks/useUser.ts | 17 +- packages/react/src/hooks/useUsers.ts | 25 +-- packages/react/src/queries/fees.ts | 20 --- packages/react/src/queries/index.ts | 9 - packages/react/src/queries/payments.ts | 64 ------- packages/react/src/queries/platform.ts | 57 ------- packages/react/src/queries/proposals.ts | 114 ------------- packages/react/src/queries/reviews.ts | 24 --- packages/react/src/queries/users.ts | 158 ------------------ 17 files changed, 55 insertions(+), 534 deletions(-) delete mode 100644 packages/react/src/queries/fees.ts delete mode 100644 packages/react/src/queries/index.ts delete mode 100644 packages/react/src/queries/payments.ts delete mode 100644 packages/react/src/queries/platform.ts delete mode 100644 packages/react/src/queries/proposals.ts delete mode 100644 packages/react/src/queries/reviews.ts delete mode 100644 packages/react/src/queries/users.ts diff --git a/packages/react/package.json b/packages/react/package.json index 2a52bf4..e213eff 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -19,7 +19,7 @@ "typescript": "^5.2.2" }, "dependencies": { - "@talentlayer/client": "^0.1.6", + "@talentlayer/client": "^0.1.7", "net": "^1.0.2", "react": "^18.2.0", "wagmi": "^1.4.2" diff --git a/packages/react/src/contexts/talentLayerContext.tsx b/packages/react/src/contexts/talentLayerContext.tsx index b36a74c..49d5bf0 100644 --- a/packages/react/src/contexts/talentLayerContext.tsx +++ b/packages/react/src/contexts/talentLayerContext.tsx @@ -83,7 +83,7 @@ export function TalentLayerProvider(props: TalentLayerProviderProps) { } useEffect(() => { - if (chainId && account.address) { + if (chainId && account.address && !talentLayerClient) { const tlClient = new TalentLayerClient(config); setTalentLayerClient(tlClient); } diff --git a/packages/react/src/hooks/useFees.ts b/packages/react/src/hooks/useFees.ts index 92430e7..c24d690 100644 --- a/packages/react/src/hooks/useFees.ts +++ b/packages/react/src/hooks/useFees.ts @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { IFees } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; export default function useFees( originServicePlatformId: string, @@ -25,17 +24,12 @@ export default function useFees( originValidatedProposalFeeRate: 0, }; try { - const query = queries.fees.getProtocolAndPlatformsFees( - originServicePlatformId, - originValidatedProposalPlatformId, - ); - const response = await talentLayer.subgraph.query(query); - const data = response.data; - - if (data) { - fees.protocolEscrowFeeRate = data.protocols[0].protocolEscrowFeeRate; - fees.originServiceFeeRate = data.servicePlatform.originServiceFeeRate; - fees.originValidatedProposalFeeRate = data.proposalPlatform.originValidatedProposalFeeRate; + 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); diff --git a/packages/react/src/hooks/useMintFee.ts b/packages/react/src/hooks/useMintFee.ts index 9b2ba70..cd42e4f 100644 --- a/packages/react/src/hooks/useMintFee.ts +++ b/packages/react/src/hooks/useMintFee.ts @@ -1,15 +1,6 @@ import { useEffect, useState } from 'react'; import useTalentLayer from './useTalentLayer'; -const query = ` -{ - protocols { - userMintFee, - shortHandlesMaxPrice - } -} - `; - export default function useMintFee() { const [mintFee, setMintFee] = useState(0); const [shortHandlesMaxPrice, setShortHandlesMaxPrice] = useState(0); @@ -18,11 +9,10 @@ export default function useMintFee() { async function loadData() { try { - const response = await talentLayer.subgraph.query(query); - const data = response.data; + const response = await talentLayer.client?.profile.getMintFees(); - if (data) { - const protocol = data.protocols[0]; + if (response) { + const protocol = response.protocols[0]; setMintFee(protocol.userMintFee); setShortHandlesMaxPrice(protocol.shortHandlesMaxPrice); } diff --git a/packages/react/src/hooks/usePaymentsByService.ts b/packages/react/src/hooks/usePaymentsByService.ts index 699b519..aa4e43d 100644 --- a/packages/react/src/hooks/usePaymentsByService.ts +++ b/packages/react/src/hooks/usePaymentsByService.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; import { IPayment, PaymentTypeEnum } from '../types'; -import queries from '../queries'; import useTalentLayer from './useTalentLayer'; export default function usePaymentsByService(id: string, paymentType?: PaymentTypeEnum) { @@ -12,11 +11,10 @@ export default function usePaymentsByService(id: string, paymentType?: PaymentTy async function loadData() { try { - const query = queries.payments.getPaymentsByService(id, paymentType); - const response = await talentLayer.subgraph.query(query); + const response = await talentLayer.client?.escrow.getByService(id) - if (response?.data?.data?.payments) { - setPayments(response.data.data.payments); + if (response) { + setPayments(response); } } catch (error: any) { console.error(error); diff --git a/packages/react/src/hooks/usePaymentsForUser.ts b/packages/react/src/hooks/usePaymentsForUser.ts index c551328..a9f1f5a 100644 --- a/packages/react/src/hooks/usePaymentsForUser.ts +++ b/packages/react/src/hooks/usePaymentsForUser.ts @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { IPayment } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; export default function usePaymentsForUser(options: { id: string; @@ -27,26 +26,28 @@ export default function usePaymentsForUser(options: { const end = endDate ? new Date(endDate).getTime() / 1000 : ''; async function loadData() { + if (!talentLayer.client) return; + setLoading(true); try { - const query = queries.payments.getPaymentsForUser( - id, - total, - 0, - start.toString(), - end.toString(), + const response = await talentLayer.client?.profile.getPayments( + options.id, + options.numberPerPage, + offset, + options.startDate, + options.endDate, ); - const response = talentLayer.subgraph.query(query); - if (response && response.data && response.data.data) { - setPayments([...response.data.data.payments]); + if (response) { + setPayments([...response]); - if (response.data.data.payments.length < total) { + if (response.length < total) { setHasMoreData(false); } } } catch (error: any) { console.error(error); + setError(error); } finally { setLoading(false); } diff --git a/packages/react/src/hooks/useProposals.ts b/packages/react/src/hooks/useProposals.ts index e70c191..291c880 100644 --- a/packages/react/src/hooks/useProposals.ts +++ b/packages/react/src/hooks/useProposals.ts @@ -1,13 +1,12 @@ import { useEffect, useState } from 'react'; import { IProposal, OnlyOne } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; export default function useProposals( filter?: OnlyOne<{ serviceId?: string; userId?: string; - }> , + }>, ) { const [data, setData] = useState(); const [loading, setLoading] = useState(true); @@ -18,22 +17,21 @@ export default function useProposals( async function loadData() { setLoading(true); if (!talentLayer.client) return; - + try { if (filter === undefined) return setData(undefined); - + if (filter.serviceId !== undefined) { - const query = queries.proposals.getAllProposalsByServiceId(filter.serviceId); - const proposals = await talentLayer.subgraph.query(query); + const proposals = await talentLayer.client.proposal.getByServiceId(filter.serviceId); return setData(proposals.data.proposals as IProposal[]); } - + if (filter.userId !== undefined) { - const query = queries.proposals.getAllProposalsByUser(filter.userId); - const proposals = await talentLayer.subgraph.query(query); + 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); @@ -42,10 +40,10 @@ export default function useProposals( 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 index 50a8232..edf30d1 100644 --- a/packages/react/src/hooks/useReviews.ts +++ b/packages/react/src/hooks/useReviews.ts @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; -import { IProposal, IReview } from '../types'; +import { IReview } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; export default function useReviews(serviceId: string) { const [data, setData] = useState(); @@ -15,12 +14,9 @@ export default function useReviews(serviceId: string) { if (!talentLayer.client) return; try { - const query = queries.reviews.getReviewsByService(serviceId); - const reviews = await talentLayer.subgraph.query(query); - console.log(reviews.data) - return setData(reviews.data); + const reviews = await talentLayer.client.review.getByService(serviceId); - throw new Error('Proposal not found'); + return setData(reviews.data); } catch (error: any) { console.error(error); setError(error); diff --git a/packages/react/src/hooks/useUser.ts b/packages/react/src/hooks/useUser.ts index 5eeb716..1331cf3 100644 --- a/packages/react/src/hooks/useUser.ts +++ b/packages/react/src/hooks/useUser.ts @@ -1,9 +1,8 @@ import { useEffect, useState } from 'react'; import { IUser, OnlyOne } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; -export default function useUser(options: OnlyOne<{ userId: string; address: string }>) { +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); @@ -12,18 +11,16 @@ export default function useUser(options: OnlyOne<{ userId: string; address: stri async function loadData() { if (!talentLayer.client) return; - + try { if (options.userId) { - const query = queries.users.getUserById(options.userId); - const response = await talentLayer.subgraph.query(query); - setUser(response.data?.user); + const response = await talentLayer.client.profile.getById(options.userId); + setUser(response); } if (options.address) { - const query = queries.users.getUserByAddress(options.address); - const response = await talentLayer.subgraph.query(query); - setUser(response.data?.user); + const response = await talentLayer.client.profile.getByAddress(options.address); + setUser(response); } setLoading(true); @@ -37,7 +34,7 @@ export default function useUser(options: OnlyOne<{ userId: string; address: stri useEffect(() => { loadData(); - }, [options]); + }, []); return [user, loading, error] as const; } diff --git a/packages/react/src/hooks/useUsers.ts b/packages/react/src/hooks/useUsers.ts index 1ba05a8..1380b6c 100644 --- a/packages/react/src/hooks/useUsers.ts +++ b/packages/react/src/hooks/useUsers.ts @@ -1,13 +1,8 @@ import { useEffect, useState } from 'react'; -import { getUsers } from '../queries/users'; import { IUser } from '../types'; import useTalentLayer from './useTalentLayer'; -import queries from '../queries'; -export default function useUsers (options: { - searchQuery?: string; - numberPerPage?: number; -}) { +export default function useUsers(options: { searchQuery?: string; numberPerPage?: number }) { const [users, setUsers] = useState([]); const [hasMoreData, setHasMoreData] = useState(true); const [offset, setOffset] = useState(0); @@ -21,14 +16,13 @@ export default function useUsers (options: { try { setLoading(true); - const query = queries.users.getUsers(options.numberPerPage, offset, options.searchQuery); - const response = await talentLayer.subgraph.query(query); + const response = await talentLayer.client.profile.getBy({}); - if (offset === 0) { - setUsers(response.data.users || []); - } else { - setUsers([...users, ...response.data.users]); - } + if (offset === 0) { + setUsers(response.data.users || []); + } else { + setUsers([...users, ...response.data.users]); + } if (options.numberPerPage && response?.data?.users.length < options.numberPerPage) { setHasMoreData(false); @@ -37,7 +31,7 @@ export default function useUsers (options: { } } catch (error: any) { console.error(error); - setError(error) + setError(error); } finally { setLoading(false); } @@ -57,5 +51,4 @@ export default function useUsers (options: { } return [{ items: users, hasMoreData, loadMore } as const, loading, error] as const; -}; - +} diff --git a/packages/react/src/queries/fees.ts b/packages/react/src/queries/fees.ts deleted file mode 100644 index 69e70b8..0000000 --- a/packages/react/src/queries/fees.ts +++ /dev/null @@ -1,20 +0,0 @@ -export function getProtocolAndPlatformsFees( - originServicePlatformId: string, - originValidatedProposalPlatformId: string, -) { - const query = ` - { - protocols { - protocolEscrowFeeRate - } - servicePlatform: platform(id:${originServicePlatformId}){ - originServiceFeeRate - } - proposalPlatform: platform(id:${originValidatedProposalPlatformId}){ - originValidatedProposalFeeRate - } - } - `; - - return query; -} diff --git a/packages/react/src/queries/index.ts b/packages/react/src/queries/index.ts deleted file mode 100644 index 7832644..0000000 --- a/packages/react/src/queries/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as fees from './fees'; -import * as payments from './payments'; -import * as platform from './platform'; -import * as proposals from './proposals'; -import * as reviews from './reviews'; -import * as users from './users'; - -const queries = { fees, payments, platform, proposals, reviews, users }; -export default queries \ No newline at end of file diff --git a/packages/react/src/queries/payments.ts b/packages/react/src/queries/payments.ts deleted file mode 100644 index 76f39b8..0000000 --- a/packages/react/src/queries/payments.ts +++ /dev/null @@ -1,64 +0,0 @@ -export function getPaymentsByService(serviceId: string, paymentType?: string) { - let condition = `where: {service: "${serviceId}"`; - paymentType ? (condition += `, paymentType: "${paymentType}"`) : ''; - condition += '}, orderBy: id, orderDirection: asc'; - const query = ` - { - payments(${condition}) { - id - amount - rateToken { - address - decimals - name - symbol - } - paymentType - transactionHash - createdAt - } - } - `; - return query; -} - -export function getPaymentsForUser( - userId: string, - numberPerPage?: number, - offset?: number, - startDate?: string, - endDate?: string, -) { - const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : ''; - - const startDataCondition = startDate ? `, createdAt_gte: "${startDate}"` : ''; - const endDateCondition = endDate ? `, createdAt_lte: "${endDate}"` : ''; - - const query = ` - { - payments(where: { - service_: {seller: "${userId}"} - ${startDataCondition} - ${endDateCondition} - }, - orderBy: createdAt orderDirection: desc ${pagination} ) { - id, - rateToken { - address - decimals - name - symbol - } - amount - transactionHash - paymentType - createdAt - service { - id, - cid - } - } - } - `; - return query; -} diff --git a/packages/react/src/queries/platform.ts b/packages/react/src/queries/platform.ts deleted file mode 100644 index 4455c8c..0000000 --- a/packages/react/src/queries/platform.ts +++ /dev/null @@ -1,57 +0,0 @@ -const platformFields = ` - id - address - name - createdAt - updatedAt - feePayments - totalPlatformGains - feeClaims - originServiceFeeRate - originValidatedProposalFeeRate - servicePostingFee - proposalPostingFee - arbitrator - arbitratorExtraData - arbitrationFeeTimeout - cid - description - signer -`; - -const platformDescriptionFields = ` - id - about - website - platform - video_url - image_url -`; - -export function getPlatform(id: string) { - const query = ` - { - platform(id: ${id}) { - ${platformFields} - description { - ${platformDescriptionFields} - } - } - } - `; - return query; -} - -export function getPlatformsByOwner(addressOwner: string) { - const query = ` - { - platforms(where: {address: "${addressOwner}"}) { - ${platformFields} - description { - ${platformDescriptionFields} - } - } - } - `; - return query; -} diff --git a/packages/react/src/queries/proposals.ts b/packages/react/src/queries/proposals.ts deleted file mode 100644 index 7f33af8..0000000 --- a/packages/react/src/queries/proposals.ts +++ /dev/null @@ -1,114 +0,0 @@ -export function getAllProposalsByServiceId(id: string) { - const query = ` - { - proposals(where: {service_: {id: "${id}"}}) { - service { - id, - cid - buyer { - id - } - platform { - id - } - } - cid - id - status - rateToken { - address - decimals - name - symbol - } - rateAmount - createdAt - updatedAt - seller { - id - handle - address - cid - rating - userStats { - numReceivedReviews - } - } - description { - id - about - expectedHours - startDate - video_url - } - expirationDate - platform { - id - } - } - } - `; - return query; -} - -export function getAllProposalsByUser(id: string) { - const query = ` - { - proposals(where: {seller: "${id}", status: "Pending"}) { - id - rateAmount - rateToken { - address - decimals - name - symbol - } - status - cid - createdAt - seller { - id - handle - } - service { - id - cid - createdAt - buyer { - id - handle - } - } - description { - id - about - expectedHours - startDate - video_url - } - expirationDate - } - } - `; - return query; -} - -export function getProposalById(id: string) { - const query = ` - { - proposals(where: {id: "${id}"}) { - rateToken { - address - } - rateAmount - description { - about - video_url - } - status - expirationDate - } - } - `; - return query; -} diff --git a/packages/react/src/queries/reviews.ts b/packages/react/src/queries/reviews.ts deleted file mode 100644 index 5c00591..0000000 --- a/packages/react/src/queries/reviews.ts +++ /dev/null @@ -1,24 +0,0 @@ -export function getReviewsByService(serviceId: string) { - const query = ` - { - reviews(where: { service: "${serviceId}" }, orderBy: id, orderDirection: desc) { - id - rating - createdAt - service { - id - status - } - to { - id - handle - } - description{ - id - content - } - } - } - `; - return query; -} diff --git a/packages/react/src/queries/users.ts b/packages/react/src/queries/users.ts deleted file mode 100644 index 1f2dd5e..0000000 --- a/packages/react/src/queries/users.ts +++ /dev/null @@ -1,158 +0,0 @@ -export function getUsers(numberPerPage?: number, offset?: number, searchQuery?: string) { - const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : ''; - let condition = ', where: {'; - condition += searchQuery ? `, handle_contains_nocase: "${searchQuery}"` : ''; - condition += '}'; - - const query = ` - { - users(orderBy: rating, orderDirection: desc ${pagination} ${condition}) { - id - address - handle - userStats { - numReceivedReviews - } - rating - } - } - `; - return query; -} - -export function getUserById(id: string) { - const query = ` - { - user(id: "${id}") { - id - address - handle - rating - delegates - userStats { - numReceivedReviews - } - updatedAt - createdAt - description { - about - role - name - country - headline - id - image_url - video_url - title - timezone - skills_raw - web3mailPreferences{ - activeOnNewService - activeOnNewProposal - activeOnProposalValidated - activeOnFundRelease - activeOnReview - activeOnPlatformMarketing - activeOnProtocolMarketing - } - } - } - } - `; - return query; -} - -export function getUserByAddress(address: string) { - const query = ` - { - users(where: {address: "${address.toLocaleLowerCase()}"}, first: 1) { - id - address - handle - rating - delegates - userStats { - numReceivedReviews - } - updatedAt - createdAt - description { - about - role - name - country - headline - id - image_url - video_url - title - timezone - skills_raw - web3mailPreferences{ - activeOnNewService - activeOnNewProposal - activeOnProposalValidated - activeOnFundRelease - activeOnReview - activeOnPlatformMarketing - activeOnProtocolMarketing - } - } - } - } - `; - return query; -} - -export function getUserTotalGains(id: string) { - const query = ` - { - user(id: "${id}") { - totalGains{ - id - totalGain - token { - id - name - symbol - decimals - } - } - } - } - `; - return query; -} - -export function getUserByIds(ids: string[]) { - const query = ` - { - users(where: {id_in: [${ids.join(',')}]}) { - id - address - handle - rating - delegates - userStats { - numReceivedReviews - } - updatedAt - createdAt - description { - about - role - name - country - headline - id - image_url - video_url - title - timezone - skills_raw - } - } - } - `; - return query; -}