From 942894d81d0ab41b04ce7dbe29d467ac54a670ca Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Jul 2024 06:16:12 -0400 Subject: [PATCH 01/21] Post job Form and input changes --- .../src/app/dashboard/post-job/JobSummary.tsx | 14 +- .../app/dashboard/post-job/PostJobPage.tsx | 153 +++++++++++------- .../components/JobActions/AcceptButton.tsx | 2 +- website/src/components/Radio.tsx | 2 + website/src/components/TagsInput.tsx | 11 +- website/src/service/FormsTypes.ts | 5 + 6 files changed, 115 insertions(+), 72 deletions(-) diff --git a/website/src/app/dashboard/post-job/JobSummary.tsx b/website/src/app/dashboard/post-job/JobSummary.tsx index 792cad99..c99d738d 100644 --- a/website/src/app/dashboard/post-job/JobSummary.tsx +++ b/website/src/app/dashboard/post-job/JobSummary.tsx @@ -23,14 +23,16 @@ const JobSummary: React.FC = ({formInputs, submitJob, isPendin
{formInputs.map((inputData, index) => ( + inputData.inputInfo && (
-
- {inputData.label} -
-
- {inputData.inputInfo} -
+
+ {inputData.label} +
+
+ {inputData.inputInfo} +
+ ) ) )}
diff --git a/website/src/app/dashboard/post-job/PostJobPage.tsx b/website/src/app/dashboard/post-job/PostJobPage.tsx index e747a656..263c1988 100644 --- a/website/src/app/dashboard/post-job/PostJobPage.tsx +++ b/website/src/app/dashboard/post-job/PostJobPage.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import React, { ChangeEvent, FormEvent } from 'react' import { ethers } from 'ethers' import { useAccount, @@ -26,7 +26,7 @@ import { zeroAddress } from 'viem' import useUsers from '@/hooks/useUsers' import useArbitrators from '@/hooks/useArbitrators' import { ComboBox } from '@/components/ComboBox' -import { ComboBoxOption, JobFormInputData } from '@/service/FormsTypes' +import { ComboBoxOption, JobFormInputData, Tag } from '@/service/FormsTypes' import JobSummary from './JobSummary' import Image from 'next/image' import moment from 'moment' @@ -34,6 +34,29 @@ import TagsInput from '@/components/TagsInput' import { BsInfoCircle } from 'react-icons/bs' import useShortenText from '@/hooks/useShortenText' +interface FieldValidation { + required?: boolean; + minLength?: number; + pattern?: RegExp; + custom?: (value: string) => string; +} + +const validateField = (value: string, validation: FieldValidation): string => { + if (validation.required && !value) { + return 'This field is required'; + } + if (validation.minLength && value.length < validation.minLength) { + return `Must be at least ${validation.minLength} characters long`; + } + if (validation.pattern && !validation.pattern.test(value)) { + return 'Invalid format'; + } + if (validation.custom) { + return validation.custom(value); + } + return ''; +}; + function PostJobPage() { const { address } = useAccount(); const { @@ -50,27 +73,18 @@ function PostJobPage() { const arbitratorAddresses = [zeroAddress, ...(arbitrators?.map((worker) => worker.address_) ?? [])]; const arbitratorNames = ["None", ...(arbitrators?.map((worker) => worker.name) ?? [])]; const arbitratorFees = ["0", ...(arbitrators?.map((worker) => worker.fee) ?? [])]; - console.log(arbitrators) + console.log(arbitrators, 'arbitrators') const [selectedToken, setSelectedToken] = useState(tokens[0]); const multipleApplicantsValues = ['Yes', 'No'] - - const tags = [ - { id: 0, name: 'None' }, - { id: 1, name: 'Web' }, - { id: 2, name: 'Design' }, - { id: 3, name: 'Translation' }, - { id: 4, name: 'Counting' }, - { id: 5, name: 'Marketing' }, - ] - const unitsDeliveryTime = [ - { id: 0, name: 'Minutes' }, - { id: 1, name: 'Hours' }, - { id: 2, name: 'Days' }, - { id: 3, name: 'Weeks' }, - { id: 4, name: 'Months' }, + { id: 0, name: 'minutes' }, + { id: 1, name: 'hours' }, + { id: 2, name: 'days' }, + { id: 3, name: 'weeks' }, + { id: 4, name: 'months' }, + { id: 5, name: 'years' }, ] const categories = [ @@ -87,17 +101,18 @@ function PostJobPage() { const [showSummary, setShowSummary] = useState(false); - const [title, setTitle] = useState(''); + + const [title, setTitle] = useState(''); const [deliveryMethod, setDeliveryMethod] = useState(''); - const [description, setDescription] = useState(''); + const [description, setDescription] = useState(''); // const [amount, setAmount] = useState(ethers.formatUnits(0, 0)); const [amount, setAmount] = useState(''); const [deadline, setDeadline] = useState(); const [multipleApplicants, setMultipleApplicants] = useState(multipleApplicantsValues[1]) const [arbitratorRequired, setArbitratorRequired] = useState(multipleApplicantsValues[0]) const [selectedUnitTime, setselectedUnitTime] = useState() - const [selectedTag, setselectedTag] = useState(tags[0]) - const [selectedCategory, setSelectedCategory] = useState<{id: string, name: string}>(categories[0]); + const [tags, setTags] = useState([]); + const [selectedCategory, setSelectedCategory] = useState<{id: string, name: string}>(); const [selectedWorkerAddress, setsSelectedWorkerAddress] = useState(undefined); const [selectedArbitratorAddress, setsSelectedArbitratorAddress] = useState(); @@ -151,6 +166,7 @@ function PostJobPage() { async function postJobClick() { if (!deadline) return if (!amount) return + if (!selectedCategory) return setPostButtonDisabled(true); console.log('posting job', title, description, selectedToken?.id, amount, deadline); @@ -165,7 +181,7 @@ function PostJobPage() { title, contentHash as `0x${string}`, multipleApplicants === 'Yes', - [selectedCategory.id, ...(selectedTag.id !== 0 ? [selectedTag.name] : [])], + [selectedCategory.id, ...tags.map(tag => tag.name)], selectedToken?.id! as `0x${string}`, ethers.parseUnits(amount, selectedToken?.decimals!), deadline, @@ -178,21 +194,47 @@ function PostJobPage() { console.log('writeContract', w); } + const [titleError, setTitleError] = useState(''); + const [descriptionError, setDescriptionError] = useState(''); + + const handleInputChange = (setter: React.Dispatch>, errorSetter: React.Dispatch>, validation: FieldValidation) => (e: ChangeEvent) => { + const value = e.target.value; + setter(value); + const errorMessage = validateField(value, validation); + errorSetter(errorMessage); + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + // Validate all fields before submission + const titleValidationMessage = validateField(title, { required: true, minLength: 3 }); + const descriptionValidationMessage = validateField(description, { required: true, minLength: 10 }); + + setTitleError(titleValidationMessage); + setDescriptionError(descriptionValidationMessage); + + if (!titleValidationMessage && !descriptionValidationMessage) { + // Proceed with form submission + console.log('Form is valid'); + } else { + console.log('Form has errors'); + } + }; const formInputs: JobFormInputData[] = [ { label: 'Job Title', inputInfo: title }, { label: 'Description', inputInfo: description }, { label: 'Multiple Applicants', inputInfo: multipleApplicants}, - { label: 'Category', inputInfo: selectedTag?.name }, { label: 'Category', inputInfo: selectedCategory?.name }, + { label: 'Tags', inputInfo: tags.map(tag => tag.name).join(', ') }, { label: 'Token', inputInfo:
{selectedToken?.id}{selectedToken?.symbol}Chain Icon
}, { label: 'Price', inputInfo: <>{amount} }, - { label: 'Deadline', inputInfo: moment.duration(deadline, "days").humanize() }, { label: 'Delivery Method', inputInfo: deliveryMethod }, + { label: 'Deadline', inputInfo: moment.duration(deadline, selectedUnitTime?.name as moment.unitOfTime.DurationConstructor).humanize() }, + { label: 'Arbitrator Required', inputInfo: arbitratorRequired}, { label: 'Arbitrator Address', inputInfo: selectedArbitratorAddress }, { label: 'Worker Address', inputInfo: selectedWorkerAddress }, ]; - return (
{!showSummary && ( @@ -210,25 +252,27 @@ function PostJobPage() { name="title" value={title} placeholder='Title' - onChange={(e) => setTitle(e.target.value)} + onChange={handleInputChange(setTitle, setTitleError, { required: true, minLength: 3 })} /> + {titleError &&
{titleError}
}