From c8a64b72260852ebccce69431571c82801e9083c Mon Sep 17 00:00:00 2001 From: Vini Black Date: Sat, 7 Oct 2023 18:53:54 -0300 Subject: [PATCH 1/3] Creating a README for the project --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db279ea..9bbbe6b 100644 --- a/README.md +++ b/README.md @@ -1 +1,57 @@ -# web3task-front \ No newline at end of file +# Web3Task-Front + +This project is the front end of the [web3task-contracts](https://github.com/w3b3d3v/web3task-contracts) protocol. + +web3task aims to solve the problem of monetized contributions by splitting the work and requirements, just like GitHub problems, where the company creates the problems and offers a bounty for users to solve them according to the company's terms. + +## Current Status + +The project is currently in the MVP stage. +If you're a programmer and want to contribute, we have a [backlog](https://github.com/orgs/w3b3d3v/projects/6/views/2) where you can see what you can help with. + +## Disputes + +We recognize that disputes are common in this type of service, so we are not primarily working on a solution to this problem, as many people have tried and failed. Instead, our solution is a gamified approach for both task creators and task assignees. Delivering before the deadline will give the assignee a better score, while completing the created tasks will also give the task creator a profile score. This score is used to rank users on the platform, and the higher the score, the more trustworthy the user. Starting a dispute is considered a risky and unworthy move by both sides, as disputes will drastically reduce the score of both sides or the side that is wrong, according to the DAO's Final Saying. + + +## Bounties + +The task creator can create a task and set a bounty for it, and the task assignee can start the task and submit it for review. If the task creator approves the task, the bounty will be sent to the task assignee, otherwise the task creator can cancel the task and get the bounty back. + + +## Ranking + +The ranking system is based on the user's score, which is calculated based on the following factors + +- Delivery time before the deadline +- Amount of the bounty +- Disputes during the execution of the task + +The points are only applied after the task has been completed. + +## Features +The following features are currently implemented + +- Create a task +- View all created tasks +- Viewing tasks for a specific user +- Perform task filtering + +## Installation + +```bash +yarn +``` + +## Usage +To run the frontend, you need to run a local node. To do this, you need to set up the [web3task-contracts](https://github.com/w3b3d3v/web3task-contracts) project on your machine. + +```bash +yarn start +``` + +## Contact + +Advisor: 0xneves (@ownerlessinc) + +This project is licensed under the MIT License, feel free to use and contribute. If you have any questions, please contact us via our [discord](https://discord.gg/web3dev) in the `pod-labs` channel. \ No newline at end of file From 4b59529d3e33fdbbe0e2684638bc76a3c9372de2 Mon Sep 17 00:00:00 2001 From: Vini Black Date: Sat, 7 Oct 2023 19:20:18 -0300 Subject: [PATCH 2/3] Adjust indents --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9bbbe6b..37d07d7 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,10 @@ If you're a programmer and want to contribute, we have a [backlog](https://githu We recognize that disputes are common in this type of service, so we are not primarily working on a solution to this problem, as many people have tried and failed. Instead, our solution is a gamified approach for both task creators and task assignees. Delivering before the deadline will give the assignee a better score, while completing the created tasks will also give the task creator a profile score. This score is used to rank users on the platform, and the higher the score, the more trustworthy the user. Starting a dispute is considered a risky and unworthy move by both sides, as disputes will drastically reduce the score of both sides or the side that is wrong, according to the DAO's Final Saying. - ## Bounties The task creator can create a task and set a bounty for it, and the task assignee can start the task and submit it for review. If the task creator approves the task, the bounty will be sent to the task assignee, otherwise the task creator can cancel the task and get the bounty back. - ## Ranking The ranking system is based on the user's score, which is calculated based on the following factors @@ -30,6 +28,7 @@ The ranking system is based on the user's score, which is calculated based on th The points are only applied after the task has been completed. ## Features + The following features are currently implemented - Create a task @@ -44,6 +43,7 @@ yarn ``` ## Usage + To run the frontend, you need to run a local node. To do this, you need to set up the [web3task-contracts](https://github.com/w3b3d3v/web3task-contracts) project on your machine. ```bash @@ -54,4 +54,4 @@ yarn start Advisor: 0xneves (@ownerlessinc) -This project is licensed under the MIT License, feel free to use and contribute. If you have any questions, please contact us via our [discord](https://discord.gg/web3dev) in the `pod-labs` channel. \ No newline at end of file +This project is licensed under the MIT License, feel free to use and contribute. If you have any questions, please contact us via our [discord](https://discord.gg/web3dev) in the `pod-labs` channel. From e8b11a737c35e54b3f872fccd06cffa79e5fe99c Mon Sep 17 00:00:00 2001 From: Vini Black Date: Wed, 18 Oct 2023 01:35:17 -0300 Subject: [PATCH 3/3] visually improving form validation messages --- .../Tasks/settings/CreateTask.tsx | 511 +++++++++++------- 1 file changed, 302 insertions(+), 209 deletions(-) diff --git a/src/content/applications/Tasks/settings/CreateTask.tsx b/src/content/applications/Tasks/settings/CreateTask.tsx index 1235111..713b95d 100644 --- a/src/content/applications/Tasks/settings/CreateTask.tsx +++ b/src/content/applications/Tasks/settings/CreateTask.tsx @@ -1,39 +1,40 @@ -import { useEffect, useState } from 'react'; +import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; -import { yupResolver } from '@hookform/resolvers/yup'; +import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; -import TextField from '@mui/material/TextField'; -import Stack from '@mui/material/Stack'; -import { AlertColor } from '@mui/material/Alert'; -import Button from '@mui/material/Button'; -import { Box, useMediaQuery, useTheme } from '@mui/material'; -import { Dayjs } from 'dayjs'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers'; +import TextField from "@mui/material/TextField"; +import Stack from "@mui/material/Stack"; +import { AlertColor } from "@mui/material/Alert"; +import Button from "@mui/material/Button"; +import { Box, useMediaQuery, useTheme } from "@mui/material"; +import { Dayjs } from "dayjs"; +import { DatePicker, DatePickerProps } from "@mui/x-date-pickers"; import { useTaskService } from "src/services/tasks-service"; import { Task } from "src/models/task"; -import SuspenseLoader from 'src/components/SuspenseLoader'; -import CoverCreateTask from '../../../../components/Cover/CoverCreateTask'; -import { useSnackBar } from 'src/contexts/SnackBarContext'; -import { Helmet } from 'react-helmet-async'; -import { BigNumber, ethers } from 'ethers'; +import SuspenseLoader from "src/components/SuspenseLoader"; +import CoverCreateTask from "../../../../components/Cover/CoverCreateTask"; +import { useSnackBar } from "src/contexts/SnackBarContext"; +import { Helmet } from "react-helmet-async"; +import { BigNumber, ethers } from "ethers"; +import dayjs from "dayjs"; +import { DateValidationError } from "@mui/x-date-pickers/models"; let newTask: Task = { status: 0, - title: '', - description: '', + title: "", + description: "", reward: BigNumber.from("0"), - endDate: BigInt(''), - authorizedRoles: [BigInt('')], - creatorRole: BigInt(''), + endDate: BigInt(""), + authorizedRoles: [BigInt("")], + creatorRole: BigInt(""), assignee: "0x0000000000000000000000000000000000000000", - metadata: '' -} - + metadata: "", +}; const CreateTask = ({ data }) => { const theme = useTheme(); - const mdDown = useMediaQuery(theme.breakpoints.down('md')); - const smDown = useMediaQuery(theme.breakpoints.down('sm')); + const mdDown = useMediaQuery(theme.breakpoints.down("md")); + const smDown = useMediaQuery(theme.breakpoints.down("sm")); const { createTask } = useTaskService(); const [task, setTask] = useState(); const [valueReward, setValueReward] = useState(); @@ -42,134 +43,181 @@ const CreateTask = ({ data }) => { const [endDate, setEndDate] = useState(); const [loading, setLoading] = useState(true); const [openError, setOpenError] = useState(false); + const [error, setError] = React.useState(null); const { showSnackBar } = useSnackBar(); const handleSnackbar = (message: string, color: AlertColor) => { - showSnackBar(message, color) + showSnackBar(message, color); }; - const schema = yup.object({ - title: yup.string().required('Mandatory field.'), - creatorRole: yup.string().required('Mandatory field.').test({ - test(value, ctx) { - let role = Number(value); - if (isNaN(role)) - return ctx.createError({ message: 'Invalid number for role.' }) - return true; - } - }), - valueReward: yup.string().required('Mandatory field.').test({ - test(value, ctx) { - let role = Number(value); - if (isNaN(role)) - return ctx.createError({ message: 'Invalid value.' }) - return true; - } - }), - authorizedRoles: yup.string().required('Mandatory field.').test({ - test(value, ctx) { - let validation = true; - let roles = value.split(','); - roles.forEach(element => { - let role = Number(element); - if (isNaN(role)) - validation = false; - }); - if (!validation) - return ctx.createError({ message: 'Invalid role number.' }); - return validation; - } - }), - assignee: yup.string().test({ - test(value, ctx) { - if (value.length != 42 || value.slice(0,2) != "0x") - return ctx.createError({ message: 'Invalid address.' }); - return true; - } - }), - metadata: yup.string().required('Mandatory field.').test({ - test(value, ctx) { - if (value.slice(0,4) == "ipfs" || value.slice(0,4) == "http") + const schema = yup + .object({ + title: yup.string().required("Mandatory field."), + creatorRole: yup + .string() + .required("Mandatory field.") + .test({ + test(value, ctx) { + let role = Number(value); + if (isNaN(role)) + return ctx.createError({ message: "Invalid number for role." }); + return true; + }, + }), + valueReward: yup + .string() + .required("Mandatory field.") + .test({ + test(value, ctx) { + let role = Number(value); + if (isNaN(role)) + return ctx.createError({ message: "Invalid value." }); + return true; + }, + }), + authorizedRoles: yup + .string() + .required("Mandatory field.") + .test({ + test(value, ctx) { + let validation = true; + let roles = value.split(","); + roles.forEach((element) => { + let role = Number(element); + if (isNaN(role)) validation = false; + }); + if (!validation) + return ctx.createError({ message: "Invalid role number." }); + return validation; + }, + }), + assignee: yup.string().test({ + test(value, ctx) { + if (value.length != 42 || value.slice(0, 2) != "0x") + return ctx.createError({ message: "Invalid address." }); return true; - - return ctx.createError({ message: 'Invalid metadata. Try ipfs.io/ipfs/...' }); + }, + }), + metadata: yup + .string() + .required("Mandatory field.") + .test({ + test(value, ctx) { + if (value.slice(0, 4) == "ipfs" || value.slice(0, 4) == "http") + return true; + + return ctx.createError({ + message: "Invalid metadata. Try ipfs.io/ipfs/...", + }); + }, + }), + description: yup + .string() + .required("Mandatory field.") + .test({ + test(value, ctx) { + if (value.length < 100) + return ctx.createError({ + message: "Invalid description. Minimum 100 characters", + }); + return true; + }, + }), + endDate: yup.string().test({ + test(value, ctx) { + let date = Date.parse(endDate); + if (isNaN(date)) return ctx.createError({ message: "Invalid date." }); + return true; + }, + }), + }) + .required(); + + const currentDate = dayjs(); + const errorMessage = React.useMemo(() => { + switch (error) { + case "minDate": { + return "Select a date later than the current date"; } - }), - description: yup.string().required('Mandatory field.').test({ - test(value, ctx) { - if (value.length < 100) - return ctx.createError({ message: 'Invalid description. Minimum 100 characters' }); - return true; + + case "invalidDate": { + return "Your date is not valid"; } - }), - endDate: yup.string().test({ - test(value, ctx) { - let date = Date.parse(endDate); - if (isNaN(date)) - return ctx.createError({ message: 'Invalid date.' }); - return true; + + default: { + return ""; } - }) - }).required(); + } + }, [error]); - - const { register, handleSubmit, formState: { errors } } = useForm({ - resolver: yupResolver(schema) + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), }); - - const logoImage = "/static/images/logo/logo-footer-" + theme.palette.mode + ".svg"; + const logoImage = + "/static/images/logo/logo-footer-" + theme.palette.mode + ".svg"; - const handleChange = (event: { target: { value: any; }; }) => { + const handleChange = (event: { target: { value: any } }) => { task.description = event.target.value; - } + }; - const handleTitle = (event: { target: { value: any; }; }) => { - task.title = (event.target.value).toString(); + const handleTitle = (event: { target: { value: any } }) => { + task.title = event.target.value.toString(); }; const handleAuthorizedRoles = (event: any) => { setAuthorizedRolesStr(event.target.value); }; - const handleCreatorRole = (event: { target: { value: any; }; }) => { + const handleCreatorRole = (event: { target: { value: any } }) => { task.creatorRole = event.target.value; }; - const handleAssignee = (event: { target: { value: any; }; }) => { - task.assignee = event.target.value == '' ? "0x0000000000000000000000000000000000000000" : event.target.value; + const handleAssignee = (event: { target: { value: any } }) => { + task.assignee = + event.target.value == "" + ? "0x0000000000000000000000000000000000000000" + : event.target.value; }; - const handleMetadata = (event: { target: { value: any; }; }) => { + const handleMetadata = (event: { target: { value: any } }) => { task.metadata = event.target.value; }; - const handleDescription = (event: { target: { value: any; }; }) => { + const handleDescription = (event: { target: { value: any } }) => { task.description = event.target.value; }; - const handleReward = (event: { target: { value: any; }; }) => { + const handleReward = (event: { target: { value: any } }) => { let reward = event.target.value; setValueReward(reward); }; - const handleExpireDate = ( value: Dayjs ) => { - setExpireDate(value) - let strEndDate = value.toString(); + const handleExpireDate = (value: Dayjs) => { + setExpireDate(value); + let strEndDate = value ? value.toString() : null; setEndDate(strEndDate); }; - const onSubmit = async (event: { preventDefault: () => void; }) => { + const onSubmit = async (event: { preventDefault: () => void }) => { try { - handleSnackbar('Create Task Start process initiated with success!', 'info') - let authorizedRoles: string[] = (authorizedRolesStr).split(','); - const splittedRoles: readonly bigint[] = authorizedRoles.map(str => BigInt(str)); + handleSnackbar( + "Create Task Start process initiated with success!", + "info" + ); + let authorizedRoles: string[] = authorizedRolesStr.split(","); + const splittedRoles: readonly bigint[] = authorizedRoles.map((str) => + BigInt(str) + ); task.authorizedRoles = splittedRoles; task.reward = ethers.utils.parseEther(valueReward); let expireTimestamp = expireDate.unix(); task.endDate = BigInt(expireTimestamp); await createTask(task); - } catch (error) { console.log("Error when submitting the createTask form: ", error); setOpenError(true); @@ -180,10 +228,9 @@ const CreateTask = ({ data }) => { if (loading && data != undefined) { setTask(data); setLoading(false); - } - else { + } else { if (loading) { - setTask(newTask) + setTask(newTask); setLoading(false); } } @@ -194,117 +241,163 @@ const CreateTask = ({ data }) => { Web3Task - Create Task - - + + - - + display={"flex"} + justifyContent={"center"} + alignItems={"center"} + height={"100%"} + flexDirection={"column"} + > + - { - loading ? : ( - - - -

{errors.title?.message}

- - -

{errors.authorizedRoles?.message}

+ {loading ? ( + + ) : ( + + + - -

{errors.creatorRole?.message}

+ - -

{errors.assignee?.message}

+ - -

{errors.metadata?.message}

+ - -

{errors.description?.message}

+ - - - Pod3LabsRecompensaIcon - -
- -

{errors.valueReward?.message}

-
-
- handleExpireDate(newValue)} - slotProps={{ - textField: { size: 'medium' }, - openPickerIcon: { style: { color: theme.palette.primary.main } }, - switchViewButton: { style: { color: 'info' } } - }} - /> -

-
-
- - + + + + Pod3LabsRecompensaIcon + +
+ +
+
+ setError(newError)} + onChange={(newValue: Dayjs) => + handleExpireDate(newValue) + } + slotProps={{ + textField: { + size: "medium", + helperText: errorMessage, + }, + openPickerIcon: { + style: { color: theme.palette.primary.main }, + }, + switchViewButton: { style: { color: "info" } }, + }} + /> +

+ {errors.endDate?.message} +

+
-
- ) - } + + +
+
+ )}
-
+
); -} +}; -export default CreateTask +export default CreateTask;