Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@talentlayer/react",
"version": "0.0.1",
"description": "The TalentLayer Client for React with abstractions for easy interaction with the TalentLayer protocol",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
"private": false,
"scripts": {
"lint": "eslint .",
"prepare": "npm run build",
"build": "tsc"
},
"devDependencies": {
"@turbo/gen": "^1.10.12",
"@types/node": "^20.6.0",
"eslint-config-custom": "*",
"tsconfig": "*",
"typescript": "^5.2.2"
},
"dependencies": {
"@talentlayer/client": "^0.1.7",
"net": "^1.0.2",
"react": "^18.2.0",
"wagmi": "^1.4.2"
}
}
22 changes: 22 additions & 0 deletions packages/react/src/components/TalentLayerError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not seeing this component being used anywhere. Do we need it?


interface TalentLayerErrorProps {
error: any;
}

export default function TalentLayerError(props: TalentLayerErrorProps) {
const { error } = props;

return (
<div style={{ zIndex: Infinity, background: '#000000', color: '#ffffff' }}>
<h1>TalentLayer Error</h1>
{(() => {
try {
return JSON.stringify(error, null, 4);
} catch {
return `Error : ${error}`;
}
})()}
</div>
);
}
116 changes: 116 additions & 0 deletions packages/react/src/contexts/talentLayerContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use client';

import React, { ContextType } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to import react here?

import { createContext, ReactNode, useEffect, useMemo, useState } from 'react';
import { useAccount } from 'wagmi';
import { IAccount, IUser, NetworkEnum } from '../types';
import { TalentLayerClient } from '@talentlayer/client';

interface TalentLayerProviderProps {
children: ReactNode;
config: ConstructorParameters<typeof TalentLayerClient>[0] & {
Copy link
Contributor

Choose a reason for hiding this comment

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

anyway to simplify the config here?
Its a little tough to understand what the type is.

account: ReturnType<typeof useAccount>;
};
}

export interface Subgraph {
query: (query: string) => any;
}

const TalentLayerContext = createContext<{
user?: IUser;
account?: IAccount;
refreshData: () => Promise<boolean>;
loading: boolean;
client: TalentLayerClient | undefined;
platformId: number;
chainId?: NetworkEnum;
subgraph: Subgraph;
}>({
user: undefined,
account: undefined,
refreshData: async () => {
return false;
},
loading: true,
client: {} as TalentLayerClient,
platformId: -1,
chainId: undefined,
subgraph: { query: _ => new Promise(resolve => resolve(null)) },
});

export function TalentLayerProvider(props: TalentLayerProviderProps) {
const { children, config } = props;
const { chainId, platformId } = config;

const account = config.account;

const [user, setUser] = useState<IUser | undefined>();
const [loading, setLoading] = useState(true);

const [talentLayerClient, setTalentLayerClient] = useState<TalentLayerClient>();

async function loadData() {
if (!talentLayerClient) return false;

if (!account.address || !account.isConnected) {
setLoading(false);
return false;
}

try {
const userResponse = await talentLayerClient.profile.getByAddress(account.address);

const currentUser = userResponse;

const platformResponse = await talentLayerClient.platform.getOne(
config.platformId.toString(),
);

const platform = platformResponse;
currentUser.isAdmin = platform?.address === currentUser?.address;

setUser(currentUser);

return true;
} catch (err: any) {
console.error(err);

return false;
} finally {
setLoading(false);
}
}

useEffect(() => {
if (chainId && account.address && !talentLayerClient) {
const tlClient = new TalentLayerClient(config);
setTalentLayerClient(tlClient);
}
}, [chainId, account.address]);

useEffect(() => {
if (!talentLayerClient) return;

loadData();
}, [talentLayerClient]);

const value = useMemo<ContextType<typeof TalentLayerContext>>(() => {
return {
user,
account: account ? account : undefined,
refreshData: loadData,
loading,
client: talentLayerClient,
chainId,
platformId,
subgraph: {
query: (query: string) => (talentLayerClient as TalentLayerClient).graphQlClient.get(query),
},
} as any;
}, [account.address, user?.id, loading]);

return <TalentLayerContext.Provider value={value}>{children}</TalentLayerContext.Provider>;
}

export default TalentLayerContext;
49 changes: 49 additions & 0 deletions packages/react/src/hooks/useFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useState } from 'react';
import { IFees } from '../types';
import useTalentLayer from './useTalentLayer';

export default function useFees(
originServicePlatformId: string,
originValidatedProposalPlatformId: string,
) {
const [fees, setFees] = useState({
protocolEscrowFeeRate: 0,
originServiceFeeRate: 0,
originValidatedProposalFeeRate: 0,
});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<any>(null);

const talentLayer = useTalentLayer();

async function loadData() {
setLoading(true);
const fees: IFees = {
protocolEscrowFeeRate: 0,
originServiceFeeRate: 0,
originValidatedProposalFeeRate: 0,
};
try {
const response = await talentLayer.client?.escrow.getProtocolAndPlatformsFees(originServicePlatformId, originValidatedProposalPlatformId)

if (response) {
fees.protocolEscrowFeeRate = response.protocols[0].protocolEscrowFeeRate;
fees.originServiceFeeRate = response.servicePlatform.originServiceFeeRate;
fees.originValidatedProposalFeeRate = response.proposalPlatform.originValidatedProposalFeeRate;
}

setFees(fees);
} catch (error: any) {
console.error(error);
setError(error);
} finally {
setLoading(false);
}
}

useEffect(() => {
loadData();
}, [originServicePlatformId, originValidatedProposalPlatformId]);

return [fees, loading, error] as const;
}
36 changes: 36 additions & 0 deletions packages/react/src/hooks/useMintFee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react';
import useTalentLayer from './useTalentLayer';

export default function useMintFee() {
const [mintFee, setMintFee] = useState(0);
const [shortHandlesMaxPrice, setShortHandlesMaxPrice] = useState(0);

const talentLayer = useTalentLayer();

async function loadData() {
try {
const response = await talentLayer.client?.profile.getMintFees();

if (response) {
const protocol = response.protocols[0];
setMintFee(protocol.userMintFee);
setShortHandlesMaxPrice(protocol.shortHandlesMaxPrice);
}
} catch (err: any) {
// eslint-disable-next-line no-console
console.error(err);
}
}

useEffect(() => {
loadData();
}, [talentLayer.chainId]);

const calculateMintFee = (handle: string) => {
const length = handle.length;
const handlePrice = length > 4 ? mintFee : shortHandlesMaxPrice / Math.pow(2, length - 1);
return handlePrice;
};

return { calculateMintFee };
}
32 changes: 32 additions & 0 deletions packages/react/src/hooks/usePaymentsByService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { IPayment, PaymentTypeEnum } from '../types';
import useTalentLayer from './useTalentLayer';

export default function usePaymentsByService(id: string, paymentType?: PaymentTypeEnum) {
const [payments, setPayments] = useState<IPayment[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(true);

const talentLayer = useTalentLayer();

async function loadData() {
try {
const response = await talentLayer.client?.escrow.getByService(id)

if (response) {
setPayments(response);
}
} catch (error: any) {
console.error(error);
setError(error);
} finally {
setLoading(false);
}
}

useEffect(() => {
loadData();
}, [id]);

return [payments, loading, error] as const;
}
72 changes: 72 additions & 0 deletions packages/react/src/hooks/usePaymentsForUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useState } from 'react';
import { IPayment } from '../types';
import useTalentLayer from './useTalentLayer';

export default function usePaymentsForUser(options: {
id: string;
numberPerPage: number;
startDate?: string;
endDate?: string;
}) {
const { id, numberPerPage } = options;
const startDate = options.startDate;
const endDate = options.endDate;

const [payments, setPayments] = useState<IPayment[]>([]);
const [hasMoreData, setHasMoreData] = useState(true);
const [offset, setOffset] = useState(1);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<any>(null);

const talentLayer = useTalentLayer();

const total = offset * numberPerPage;

const start = startDate ? new Date(startDate).getTime() / 1000 : '';
const end = endDate ? new Date(endDate).getTime() / 1000 : '';

async function loadData() {
if (!talentLayer.client) return;

setLoading(true);
try {
const response = await talentLayer.client?.profile.getPayments(
options.id,
options.numberPerPage,
offset,
options.startDate,
options.endDate,
);

if (response) {
setPayments([...response]);

if (response.length < total) {
setHasMoreData(false);
}
}
} catch (error: any) {
console.error(error);
setError(error);
} finally {
setLoading(false);
}
}

useEffect(() => {
loadData();
}, [total, id, start, end]);

useEffect(() => {
if (!!start && !!end) {
setOffset(1);
setHasMoreData(true);
}
}, [start, end]);

function loadMore() {
setOffset(offset + 1);
}

return [{ items: payments, hasMoreData, loadMore } as const, loading, error] as const;
}
37 changes: 37 additions & 0 deletions packages/react/src/hooks/usePlatform.ts
Original file line number Diff line number Diff line change
@@ -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<IPlatform | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<any>(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;
}
Loading