Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/adding gig board #9

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
30 changes: 25 additions & 5 deletions src/components/Form/ServiceForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useWeb3Modal } from '@web3modal/react';
import { BigNumberish, ethers, FixedNumber, Signer, Wallet } from 'ethers';
import { BigNumberish, ethers, FixedNumber } from 'ethers';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import { useContext, useState } from 'react';
import { useRouter } from 'next/router';
Expand Down Expand Up @@ -35,7 +35,12 @@ const initialValues: IFormValues = {
rateAmount: 0,
};

function ServiceForm() {
interface IServiceFormProps {
onChange?: (event: any) => void;
onSuccess?: () => void;
}

function ServiceForm(props: IServiceFormProps) {
const config = useConfig();
const chainId = useChainId();

Expand Down Expand Up @@ -132,6 +137,7 @@ function ServiceForm() {
cid,
signature,
);
console.log('no active delegate', tx);
}

const newId = await createMultiStepsTransactionToast(
Expand All @@ -149,9 +155,10 @@ function ServiceForm() {
setSubmitting(false);
resetForm();
if (newId) {
router.push(`/dashboard/services/${newId}`);
props?.onSuccess ? props.onSuccess() : router.push(`/dashboard/services/${newId}`);
}
} catch (error) {
console.log('tx error: ', error);
showErrorTransactionToast(error);
}
} else {
Expand All @@ -161,12 +168,16 @@ function ServiceForm() {

return (
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{({ isSubmitting, setFieldValue }) => (
{({ isSubmitting, setFieldValue, handleChange }) => (
<Form>
<div className='grid grid-cols-1 gap-6 border border-gray-700 rounded-xl p-6 bg-endnight'>
<label className='block'>
<span className='text-gray-100'>Title</span>
<Field
onChange={(e: any) => {
handleChange(e);
props.onChange && props.onChange({ [e?.target?.name]: e?.target?.value });
}}
type='text'
id='title'
name='title'
Expand All @@ -181,6 +192,10 @@ function ServiceForm() {
<label className='block'>
<span className='text-gray-100'>About</span>
<Field
onChange={(e: any) => {
handleChange(e);
props.onChange && props.onChange({ [e?.target?.name]: e?.target?.value });
}}
as='textarea'
id='about'
name='about'
Expand All @@ -204,6 +219,10 @@ function ServiceForm() {
<label className='block flex-1 mr-4'>
<span className='text-gray-100'>Amount</span>
<Field
onChange={(e: any) => {
handleChange(e);
props.onChange && props.onChange({ [e?.target?.name]: e?.target?.value });
}}
type='number'
id='rateAmount'
name='rateAmount'
Expand All @@ -222,10 +241,11 @@ function ServiceForm() {
name='rateToken'
className='mt-1 mb-1 block w-full rounded-xl border border-gray-700 bg-midnight shadow-sm focus:ring-opacity-50'
placeholder=''
onChange={(e: { target: { value: string } }) => {
onChange={(e: { target: { value: string; name: string } }) => {
const token = allowedTokenList.find(token => token.address === e.target.value);
setSelectedToken(token);
setFieldValue('rateToken', e.target.value);
props.onChange && props.onChange({ [e?.target?.name]: e?.target?.value });
}}>
<option value=''>Select a token</option>
{allowedTokenList.map((token, index) => (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function SubmitButton({
openConnectModal();
}}
type='button'
className='grow px-5 py-2 rounded-xl bg-redpraha text-white hover:bg-midnight '>
className='grow px-5 py-2 rounded-xl bg-redpraha text-white hover:bg-midnight'>
{'Connect first'}
</button>
)}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Form/skills-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
export function SkillsInput({
initialValues,
entityId,
onSelectedSkills,
}: {
initialValues?: string;
entityId: string;
onSelectedSkills?: (skills: string[]) => void;
}) {
const formikProps = useFormikContext();
const { skills: filteredSkills, fetchData: refreshSkills, query, setQuery } = useWorkxSkills();
Expand All @@ -20,6 +22,9 @@ export function SkillsInput({
const selectSkill = (value: string) => {
const newSkills = [...allSkills, value];
setAllSkills(newSkills);

// call this function in case consumer wants to hook into it
onSelectedSkills && onSelectedSkills(newSkills);
setSelectedSkill('');
setQuery('');
formikProps.setFieldValue(entityId, newSkills.toString());
Expand Down
38 changes: 38 additions & 0 deletions src/components/GigBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import useServices from '../../hooks/useServices';
import { ServiceStatusEnum } from '../../types';
import ServiceItem from '../ServiceItem';

interface IGigBoardProps {
Copy link
Contributor

@0xRomain 0xRomain Aug 21, 2023

Choose a reason for hiding this comment

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

The semantics used for the end user and the one used in the code is different. In the code, we use a more global term used also in smart contract: Service
Also I think Board is not as clear for the code and can be confusing as we also have a page which list services.
So globally, pages and components may be named: ServicesEmbed, ServicesEmbedSettings, create-services-embed

buyerId: string;
status: ServiceStatusEnum;
title: string;
}
const GigBoard = (props: IGigBoardProps) => {
const { services, loading } = useServices(
props.status,
props.buyerId,
undefined,
undefined,
undefined,
);

if (loading) {
return <div>Loading</div>;
}
if (services.length === 0) {
return <div>You have no services</div>;
Copy link
Contributor

Choose a reason for hiding this comment

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

no gigs

}
return (
<div>
<h1 className='text-title text-4xl mb-4'>{props.title}</h1>
{services.map(_service => (
<div className='my-2'>
<ServiceItem key={_service.id} service={_service} />
</div>
))}
</div>
);
};

export default GigBoard;
7 changes: 7 additions & 0 deletions src/components/Layout/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BriefcaseIcon,
ChatBubbleBottomCenterIcon,
DeviceTabletIcon,
UserGroupIcon,
UserIcon,
} from '@heroicons/react/24/outline';
Expand All @@ -10,4 +11,10 @@ export const navigation = [
{ name: 'Chat', href: '/dashboard/messaging', icon: ChatBubbleBottomCenterIcon, current: false },
{ name: 'Gigs', href: '/dashboard/services', icon: BriefcaseIcon, current: false },
{ name: 'Talents', href: '/dashboard/talents', icon: UserGroupIcon, current: false },
{
name: 'Gig Board',
href: '/dashboard/gig-board-create',
icon: DeviceTabletIcon,
current: false,
},
];
2 changes: 1 addition & 1 deletion src/hooks/useServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const useServices = (
}
};
fetchData();
}, [numberPerPage, offset, searchQuery]);
}, [numberPerPage, offset, searchQuery, buyerId, serviceStatus]);

const loadMore = () => {
numberPerPage ? setOffset(offset + numberPerPage) : '';
Expand Down
124 changes: 124 additions & 0 deletions src/pages/dashboard/gig-board-create/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { useContext, useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { parseRateAmount } from '../../../utils/web3';
import { useChainId } from '../../../hooks/useChainId';
import useAllowedTokens from '../../../hooks/useAllowedTokens';
import StarterKitContext from '../../../context/starterKit';
import Steps from '../../../components/Steps';
import ServiceForm from '../../../components/Form/ServiceForm';
import { formatDate } from '../../../utils/dates';
import { renderTokenAmountFromConfig } from '../../../utils/conversion';

interface ILiveServiceContent {
title?: string;
about?: string;
rateAmount?: string;
rateToken?: string;
}

const GigBoardCreator = () => {
const [liveServiceContent, setLiveServiceContent] = useState<ILiveServiceContent>({
title: 'Enter a title',
rateToken: '0x0000000000000000000000000000000000000000',
rateAmount: '0',
});

const chainId = useChainId();
const allowedTokenList = useAllowedTokens();
const { user } = useContext(StarterKitContext);
const router = useRouter();
const navigateToGigBoardSettings = () => router.push('/dashboard/gig-board-create/settings');

if (!user) {
return <Steps />;
}

return (
<div className='max-w-7xl mx-auto text-gray-200 sm:px-4 lg:px-0'>
<p className='flex items-center text-2xl font-medium tracking-wider mb-8 border-b w-full border-gray-700'>
Post your First Gig
</p>
<div className='grid grid-cols-2 gap-2'>
<div>
<ServiceForm
onSuccess={navigateToGigBoardSettings}
onChange={(event: ILiveServiceContent) => {
// TODO: move this to an async function expression
if (event.rateAmount) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Best practices and perf improvement:
here a new instance of your function is created on every rerender, better to use useCallback

if (liveServiceContent?.rateToken) {
const token = allowedTokenList.find(
token => token.address === liveServiceContent.rateToken,
);

if (token) {
parseRateAmount(
event.rateAmount.toString(),
liveServiceContent.rateToken,
token.decimals,
).then(_parsedRateAmount => {
setLiveServiceContent({
...liveServiceContent,
rateAmount: _parsedRateAmount.toString(),
});
});
}
}
} else {
setLiveServiceContent({ ...liveServiceContent, ...event });
}
}}
/>
<div className='mt-4 grid grid-cols-2'>
<div />
<button
type='submit'
className='rounded-xl text-white'
onClick={navigateToGigBoardSettings}>
{'>'} Skip and configure your board
</button>
</div>
</div>
<div>
<div className='flex flex-row gap-2 rounded-xl p-4 border border-gray-700 text-white bg-endnight'>
<div className='flex flex-col items-top justify-between gap-4 w-full'>
<div className='flex flex-col justify-start items-start gap-4'>
<div className='flex items-center justify-start'>
<Image
src={`/images/default-avatar-${Number(user?.id) % 9}.jpeg`}
className='w-10 mr-4 rounded-full'
width={50}
height={50}
alt='default avatar'
/>
<div className='flex flex-col'>
<p className='font-medium break-all'>{liveServiceContent?.title}</p>
<p className='text-xs text-gray-400'>
created by {user?.handle} the {formatDate(Number(Date.now()) * 1000)}
</p>
</div>
</div>
</div>
<div>
<p>{liveServiceContent.about}</p>
</div>
<div className='flex flex-row gap-4 justify-between items-center border-t border-gray-700 pt-4'>
{liveServiceContent?.rateToken && liveServiceContent?.rateAmount && (
<p className='text-gray-300 font-bold'>
{renderTokenAmountFromConfig(
chainId,
liveServiceContent.rateToken,
liveServiceContent.rateAmount,
)}
</p>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
};

export default GigBoardCreator;
Loading