diff --git a/src/Components/Inputs/TagsInput/TagsInput.tsx b/src/Components/Inputs/TagsInput/TagsInput.tsx index 621b373..2e44606 100644 --- a/src/Components/Inputs/TagsInput/TagsInput.tsx +++ b/src/Components/Inputs/TagsInput/TagsInput.tsx @@ -12,6 +12,7 @@ interface Props { input?: string } placeholder?: string + max?: number; [k: string]: any } @@ -20,6 +21,7 @@ interface Props { export default function TagsInput({ classes, placeholder = 'Write some tags', + max = 5, ...props }: Props) { @@ -41,13 +43,18 @@ export default function TagsInput({ onBlur(); } + const isDisabled = value.length >= max; + return (
setInputText(e.target.value)} diff --git a/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.stories.tsx b/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.stories.tsx new file mode 100644 index 0000000..2203721 --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.stories.tsx @@ -0,0 +1,20 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import BountyForm from './BountyForm'; + +export default { + title: 'Posts/Create Post Page/Bounty Form', + component: BountyForm, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
+ +export const Default = Template.bind({}); +Default.args = { +} + + diff --git a/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.tsx new file mode 100644 index 0000000..04346bb --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/BountyForm/BountyForm.tsx @@ -0,0 +1,184 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import Button from "src/Components/Button/Button"; +import DatePicker from "src/Components/Inputs/DatePicker/DatePicker"; +import FilesInput from "src/Components/Inputs/FilesInput/FilesInput"; +import TagsInput from "src/Components/Inputs/TagsInput/TagsInput"; +import * as yup from "yup"; +import ContentEditor from "../ContentEditor/ContentEditor"; + + +const schema = yup.object({ + title: yup + .string() + .required() + .min(10), + tags: yup + .array() + .required() + .min(1), + deadline: yup + .date() + .required(), + bounty_amount: yup + .number() + .typeError('Bounty amount must be a number') + .required() + .min(100) + .label("Bounty Amount"), + body: yup + .string() + .required() + .min(50, 'you have to write at least 10 words'), + cover_image: yup + .lazy((value: string | File[]) => { + switch (typeof value) { + case 'object': + return yup + .array() + .test("fileSize", "File Size is too large", (files) => (files as File[]).every(file => file.size <= 5242880)) + .test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed", + (files) => (files as File[]).every((file: File) => + ["image/jpeg", "image/png", "image/jpg"].includes(file.type))) + case 'string': + return yup.string().url(); + default: + return yup.mixed() + } + }) +}).required(); + +interface IFormInputs { + title: string + deadline: Date + bounty_amount: number + tags: NestedValue + cover_image: NestedValue | string + body: string +} + + + +export default function BountyForm() { + + + const formMethods = useForm({ + resolver: yupResolver(schema) as Resolver, + defaultValues: { + title: '', + tags: [], + bounty_amount: 100000, + deadline: new Date(), + body: '', + cover_image: [] + } + }); + const { handleSubmit, control, register, formState: { errors }, } = formMethods; + + const onSubmit: SubmitHandler = data => console.log(data); + + return ( + +
+
+
+ ( + + )} + /> +

{errors.cover_image?.message}

+ + +

+ Title +

+
+ +
+ {errors.title &&

+ {errors.title.message} +

} + +
+
+

+ Bounty Amount +

+
+ +

+ Sats +

+
+

{errors.bounty_amount?.message}

+
+
+

+ Deadline +

+ } + /> +

{errors.deadline?.message}

+ +
+
+ + +

+ Tags +

+ + {errors.tags &&

+ {errors.tags.message} +

} +
+ + + {errors.body &&

+ {errors.body.message} +

} +
+
+ + +
+
+
+ ) +} diff --git a/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.stories.tsx b/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.stories.tsx new file mode 100644 index 0000000..2d85f5a --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.stories.tsx @@ -0,0 +1,20 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import QuestionForm from './QuestionForm'; + +export default { + title: 'Posts/Create Post Page/Question Form', + component: QuestionForm, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
+ +export const Default = Template.bind({}); +Default.args = { +} + + diff --git a/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.tsx new file mode 100644 index 0000000..14717ee --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/QuestionForm/QuestionForm.tsx @@ -0,0 +1,125 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import Button from "src/Components/Button/Button"; +import FilesInput from "src/Components/Inputs/FilesInput/FilesInput"; +import TagsInput from "src/Components/Inputs/TagsInput/TagsInput"; +import * as yup from "yup"; +import ContentEditor from "../ContentEditor/ContentEditor"; + + +const schema = yup.object({ + title: yup.string().required().min(10), + tags: yup.array().required().min(1), + body: yup.string().required().min(50, 'you have to write at least 10 words'), + cover_image: yup.lazy((value: string | File[]) => { + switch (typeof value) { + case 'object': + return yup.array() + .test("fileSize", "File Size is too large", (files) => (files as File[]).every(file => file.size <= 5242880)) + .test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed", + (files) => (files as File[]).every((file: File) => + ["image/jpeg", "image/png", "image/jpg"].includes(file.type))) + case 'string': + return yup.string().url(); + default: + return yup.mixed() + } + }) +}).required(); + +interface IFormInputs { + title: string + tags: NestedValue + cover_image: NestedValue | string + body: string +} + + + +export default function QuestionForm() { + + + const formMethods = useForm({ + resolver: yupResolver(schema) as Resolver, + defaultValues: { + title: '', + tags: [], + body: '', + cover_image: [] + } + }); + const { handleSubmit, control, register, formState: { errors }, } = formMethods; + + const onSubmit: SubmitHandler = data => console.log(data); + + return ( + +
+
+
+ ( + + )} + /> +

{errors.cover_image?.message}

+ + +

+ Title +

+
+ +
+ {errors.title &&

+ {errors.title.message} +

} + + +

+ Tags +

+ + {errors.tags &&

+ {errors.tags.message} +

} +
+ + + {errors.body &&

+ {errors.body.message} +

} +
+
+ + +
+
+
+ ) +} diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.stories.tsx b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.stories.tsx index 53770fb..94e6776 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.stories.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.stories.tsx @@ -11,7 +11,7 @@ export default { } as ComponentMeta; -const Template: ComponentStory = (args) => +const Template: ComponentStory = (args) =>
export const Default = Template.bind({}); Default.args = { diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx index 1c33489..a7e7632 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx @@ -45,7 +45,7 @@ export default function StoryForm() { title: '', tags: [], body: '', - cover_image: '' + cover_image: [] } }); const { handleSubmit, control, register, formState: { errors }, } = formMethods; @@ -113,7 +113,7 @@ export default function StoryForm() {
) diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx index b3cc4c5..10ca7af 100644 --- a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx +++ b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx @@ -4,10 +4,8 @@ import { AiFillThunderbolt } from 'react-icons/ai' import { IoClose } from 'react-icons/io5' import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'; import { useAppSelector } from 'src/utils/hooks'; -import { gql, useMutation, useApolloClient } from "@apollo/client"; import Confetti from "react-confetti"; import { Wallet_Service } from 'src/services'; -import styles from './style.module.css' import { useWindowSize } from '@react-hookz/web'; import { useConfirmVoteMutation, useVoteMutation } from 'src/graphql'; @@ -121,7 +119,7 @@ export default function VoteCard({ onClose, direction, projectId, initVotes, ...
diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/style.module.css b/src/features/Projects/pages/ProjectPage/VoteCard/style.module.css deleted file mode 100644 index b4d054f..0000000 --- a/src/features/Projects/pages/ProjectPage/VoteCard/style.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.input::-webkit-outer-spin-button, -.input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* Firefox */ -.input[type="number"] { - -moz-appearance: textfield; -} diff --git a/src/index.tsx b/src/index.tsx index 60e6ec1..b0d76da 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import Wrapper from './utils/Wrapper'; -import './index.scss'; +import './styles/index.scss'; import App from './App'; diff --git a/src/index.scss b/src/styles/index.scss similarity index 98% rename from src/index.scss rename to src/styles/index.scss index 6c7e3c4..4eeb1bc 100644 --- a/src/index.scss +++ b/src/styles/index.scss @@ -1,4 +1,5 @@ @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap"); +@import url("./shared.scss"); @tailwind base; @tailwind components; @tailwind utilities; diff --git a/src/styles/shared.scss b/src/styles/shared.scss new file mode 100644 index 0000000..f6215c1 --- /dev/null +++ b/src/styles/shared.scss @@ -0,0 +1,10 @@ +.input-removed-arrows::-webkit-outer-spin-button, +.input-removed-arrows::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +.input-removed-arrows[type="number"] { + -moz-appearance: textfield; +} \ No newline at end of file diff --git a/src/utils/storybook/decorators.tsx b/src/utils/storybook/decorators.tsx index 8ed41a3..da12eb1 100644 --- a/src/utils/storybook/decorators.tsx +++ b/src/utils/storybook/decorators.tsx @@ -11,7 +11,7 @@ import { AnimatePresence, motion } from 'framer-motion'; // Add the global stuff first (index.ts) // ------------------------------------------- -import "src/index.scss"; +import "src/styles/index.scss"; import "react-multi-carousel/lib/styles.css"; import 'react-loading-skeleton/dist/skeleton.css' import { ApolloProvider } from '@apollo/client';