feat: add "hashtag" field to api, add images validation to FE, make launch_status an enum field

This commit is contained in:
MTG2000
2022-09-16 11:42:39 +03:00
parent 318367de2d
commit da7e909b46
8 changed files with 88 additions and 67 deletions

View File

@@ -2,18 +2,16 @@ import { Controller, useFormContext } from "react-hook-form"
import Card from "src/Components/Card/Card";
import TournamentsInput from "../TournamentsInput/TournamentsInput";
import { IListProjectForm } from "../FormContainer/FormContainer";
import { ProjectLaunchStatusEnum } from "src/graphql";
interface Props { }
export default function ExtrasTab(props: Props) {
const { register, formState: { errors, isDirty, }, control } = useFormContext<IListProjectForm>();
const { register, formState: { errors, }, control } = useFormContext<IListProjectForm>();
// usePrompt('You may have some unsaved changes. You still want to leave?', isDirty)
return (
<div className="flex flex-col gap-24">
<Card>
@@ -25,7 +23,7 @@ export default function ExtrasTab(props: Props) {
{...register("launch_status")}
type="radio"
name="launch_status"
value='wip'
value={ProjectLaunchStatusEnum.Wip}
/>
<div>
<p className="text-body4 font-medium">WIP 🛠</p>
@@ -37,7 +35,7 @@ export default function ExtrasTab(props: Props) {
{...register("launch_status")}
type="radio"
name="launch_status"
value='launched'
value={ProjectLaunchStatusEnum.Launched}
/>
<div>
<p className="text-body4 font-medium">Launched 🚀</p>

View File

@@ -1,7 +1,7 @@
import { FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form"
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { Team_Member_Role, UpdateProjectInput } from "src/graphql";
import { ProjectLaunchStatusEnum, Team_Member_Role, UpdateProjectInput } from "src/graphql";
import { PropsWithChildren, useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { usePrompt } from "src/utils/hooks";
@@ -13,38 +13,6 @@ interface Props {
}
// export interface IListProjectForm {
// id?: number
// title: string
// website: string
// tagline: string
// description: string
// thumbnail_image?: string
// cover_image?: string
// twitter?: string
// discord?: string
// github?: string
// category_id: number
// capabilities: NestedValue<string[]>
// screenshots: NestedValue<string[]>
// members: NestedValue<{
// id: number,
// name: string,
// jobTitle: string | null,
// avatar: string,
// role: Team_Member_Role,
// }[]>
// recruit_roles: NestedValue<string[]>
// launch_status: "wip" | "launched"
// tournaments: NestedValue<string[]>
// }
export type IListProjectForm = Override<UpdateProjectInput, {
members: NestedValue<{
id: number,
@@ -56,25 +24,34 @@ export type IListProjectForm = Override<UpdateProjectInput, {
capabilities: NestedValue<UpdateProjectInput['capabilities']>
recruit_roles: NestedValue<UpdateProjectInput['recruit_roles']>
tournaments: NestedValue<UpdateProjectInput['tournaments']>
cover_image: NestedValue<UpdateProjectInput['cover_image']>
thumbnail_image: NestedValue<UpdateProjectInput['thumbnail_image']>
}>
const schema: yup.SchemaOf<IListProjectForm> = yup.object({
id: yup.number().optional(),
title: yup.string().trim().required().min(2),
website: yup.string().trim().url().required(),
hashtag: yup
.string()
.required()
.matches(
/^(?:#)([A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:(?!))){0,28}(?:[A-Za-z0-9_]))?)((?: #)([A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}(?:[A-Za-z0-9_]))?))*$/,
"Invalid format for hashtag"
),
website: yup.string().trim().url().required().label("project's link"),
tagline: yup.string().trim().required().min(10),
description: yup.string().trim().required().min(50, 'Write at least 10 words descriping your project'),
thumbnail_image: imageSchema.required(),
cover_image: imageSchema.required(),
twitter: yup.string().url().ensure(),
discord: yup.string().url().ensure(),
github: yup.string().url().ensure(),
thumbnail_image: imageSchema.required("Please pick a thumbnail image").default(undefined),
cover_image: imageSchema.required("Please pick a cover image").default(undefined),
twitter: yup.string().url(),
discord: yup.string().url(),
github: yup.string().url(),
category_id: yup.number().required("Please choose a category"),
capabilities: yup.array().of(yup.string().required()).default([]),
screenshots: yup.array().of(imageSchema.required()).default([]),
members: yup.array().of(yup.object() as any).default([]),
recruit_roles: yup.array().of(yup.number().required()).default([]),
launch_status: yup.mixed().oneOf(['wip', 'launched']).default('wip'),
launch_status: yup.mixed().oneOf([ProjectLaunchStatusEnum.Wip, ProjectLaunchStatusEnum.Launched]).default(ProjectLaunchStatusEnum.Wip),
tournaments: yup.array().of(yup.number().required()).default([])
}).required();
@@ -84,6 +61,8 @@ export default function FormContainer(props: PropsWithChildren<Props>) {
const methods = useForm<IListProjectForm>({
defaultValues: {
cover_image: undefined,
thumbnail_image: undefined,
id: !!params.get('id') ? Number(params.get('id')) : undefined,
title: "",
website: "",
@@ -97,7 +76,7 @@ export default function FormContainer(props: PropsWithChildren<Props>) {
screenshots: [],
members: [],
recruit_roles: [],
launch_status: 'wip',
launch_status: ProjectLaunchStatusEnum.Wip,
tournaments: [],
},
resolver: yupResolver(schema) as Resolver<IListProjectForm>,

View File

@@ -15,11 +15,6 @@ export default function ProjectDetailsTab(props: Props) {
const { register, formState: { errors, }, control, getValues } = useFormContext<IListProjectForm>();
// usePrompt('You may have some unsaved changes. You still want to leave?', isDirty)
return (
<div className="md:col-span-2 flex flex-col gap-24">
<Card className="" defaultPadding={false}>
@@ -33,7 +28,6 @@ export default function ProjectDetailsTab(props: Props) {
onChange={e => {
onChange(e)
}}
// uploadText='Add a cover image'
/>
}
@@ -48,7 +42,16 @@ export default function ProjectDetailsTab(props: Props) {
/>
</div>
</div>
<div className="p-16 md:p-24 mt-64">
{(errors.cover_image || errors.thumbnail_image) && <div className="mb-16">
{errors.cover_image && <p className="input-error">
{errors.cover_image.message}
</p>}
{errors.thumbnail_image && <p className="input-error">
{errors.thumbnail_image.message}
</p>}
</div>}
<p className="text-body5 font-medium">
Project name<sup className="text-red-500">*</sup>
</p>
@@ -110,7 +113,20 @@ export default function ProjectDetailsTab(props: Props) {
{errors.description && <p className="input-error">
{errors.description.message}
</p>}
<p className="text-body5 font-medium mt-16">
Hashtag<sup className="text-red-500">*</sup>
</p>
<div className="input-wrapper mt-8 relative">
<input
type='text'
className="input-text"
placeholder='#my_awesome_app'
{...register("hashtag")}
/>
</div>
{errors.hashtag && <p className="input-error">
{errors.hashtag.message}
</p>}
</div>
</Card>
<Card className="">
@@ -203,7 +219,7 @@ export default function ProjectDetailsTab(props: Props) {
<Card>
<h2 className="text-body2 font-bolder">📷 Screenshots</h2>
<p className="text-body4 font-light text-gray-600 mt-8">Choose up to 4 images from your project</p>
<p className="text-body4 font-light text-gray-600 mt-8">Choose up to 4 screenshots from your project</p>
<div className="mt-24">
<Controller
control={control}

View File

@@ -1,10 +1,9 @@
import { useNavigate } from 'react-router-dom'
import Button from 'src/Components/Button/Button'
import Card from 'src/Components/Card/Card'
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'
import { useFormContext } from "react-hook-form"
import { IListProjectForm } from "../FormContainer/FormContainer";
import { useMemo, useState } from 'react'
import { useMemo } from 'react'
import { tabs } from '../../ListProjectPage'
import { NotificationsService } from 'src/services'
import { useAppDispatch } from 'src/utils/hooks';
@@ -19,15 +18,16 @@ interface Props {
export default function SaveChangesCard(props: Props) {
const { handleSubmit, formState: { errors, isDirty, }, reset, getValues, watch } = useFormContext<IListProjectForm>();
const { handleSubmit, formState: { isDirty, }, reset, getValues, watch } = useFormContext<IListProjectForm>();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const isUpdating = useMemo(() => !!getValues('id'), [getValues]);
const [update, updatingStatus] = useUpdateProjectMutation();
const [create, creatingStatus] = useCreateProjectMutation()
const isLoading = updatingStatus.loading || creatingStatus.loading
const [img, name, tagline] = watch(['thumbnail_image', 'title', 'tagline',])