From da7e909b46bc5b0d69942ba6eaa28246e01302b0 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 16 Sep 2022 11:42:39 +0300 Subject: [PATCH] feat: add "hashtag" field to api, add images validation to FE, make launch_status an enum field --- api/functions/graphql/nexus-typegen.ts | 7 ++- api/functions/graphql/schema.graphql | 11 +++- api/functions/graphql/types/project.js | 15 ++++- .../Components/ExtrasTab/ExtrasTab.tsx | 10 ++- .../FormContainer/FormContainer.tsx | 61 ++++++------------- .../ProjectDetailsTab/ProjectDetailsTab.tsx | 32 +++++++--- .../SaveChangesCard/SaveChangesCard.tsx | 8 +-- src/graphql/index.tsx | 11 +++- 8 files changed, 88 insertions(+), 67 deletions(-) diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index faed966..605b75d 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -35,8 +35,9 @@ export interface NexusGenInputs { description: string; // String! discord?: string | null; // String github?: string | null; // String + hashtag: string; // String! id?: number | null; // Int - launch_status: string; // String! + launch_status: NexusGenEnums['ProjectLaunchStatusEnum']; // ProjectLaunchStatusEnum! members: NexusGenInputs['TeamMemberInput'][]; // [TeamMemberInput!]! recruit_roles: number[]; // [Int!]! screenshots: NexusGenInputs['ImageInput'][]; // [ImageInput!]! @@ -100,8 +101,9 @@ export interface NexusGenInputs { description: string; // String! discord?: string | null; // String github?: string | null; // String + hashtag: string; // String! id?: number | null; // Int - launch_status: string; // String! + launch_status: NexusGenEnums['ProjectLaunchStatusEnum']; // ProjectLaunchStatusEnum! members: NexusGenInputs['TeamMemberInput'][]; // [TeamMemberInput!]! recruit_roles: number[]; // [Int!]! screenshots: NexusGenInputs['ImageInput'][]; // [ImageInput!]! @@ -124,6 +126,7 @@ export interface NexusGenInputs { export interface NexusGenEnums { POST_TYPE: "Bounty" | "Question" | "Story" + ProjectLaunchStatusEnum: "Launched" | "WIP" RoleLevelEnum: 3 | 0 | 1 | 2 | 4 TEAM_MEMBER_ROLE: "Admin" | "Maker" TournamentEventTypeEnum: 2 | 3 | 0 | 1 diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index fa85d9f..761d57b 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -84,8 +84,9 @@ input CreateProjectInput { description: String! discord: String github: String + hashtag: String! id: Int - launch_status: String! + launch_status: ProjectLaunchStatusEnum! members: [TeamMemberInput!]! recruit_roles: [Int!]! screenshots: [ImageInput!]! @@ -288,6 +289,11 @@ type Project { website: String! } +enum ProjectLaunchStatusEnum { + Launched + WIP +} + type Query { allCategories: [Category!]! allProjects(skip: Int = 0, take: Int = 50): [Project!]! @@ -475,8 +481,9 @@ input UpdateProjectInput { description: String! discord: String github: String + hashtag: String! id: Int - launch_status: String! + launch_status: ProjectLaunchStatusEnum! members: [TeamMemberInput!]! recruit_roles: [Int!]! screenshots: [ImageInput!]! diff --git a/api/functions/graphql/types/project.js b/api/functions/graphql/types/project.js index cf0f88b..625a22e 100644 --- a/api/functions/graphql/types/project.js +++ b/api/functions/graphql/types/project.js @@ -303,11 +303,17 @@ const TeamMemberInput = inputObjectType({ } }) +const ProjectLaunchStatusEnum = enumType({ + name: 'ProjectLaunchStatusEnum', + members: ['WIP', 'Launched'], +}); + const CreateProjectInput = inputObjectType({ name: 'CreateProjectInput', definition(t) { t.int('id') // exists in update t.nonNull.string('title'); + t.nonNull.string('hashtag'); t.nonNull.string('website'); t.nonNull.string('tagline'); t.nonNull.string('description'); @@ -329,7 +335,9 @@ const CreateProjectInput = inputObjectType({ type: TeamMemberInput }); t.nonNull.list.nonNull.int('recruit_roles'); // ids - t.nonNull.string('launch_status'); // "wip" | "launched" + t.nonNull.field('launch_status', { + type: ProjectLaunchStatusEnum + }); t.nonNull.list.nonNull.int('tournaments'); // ids } }) @@ -361,6 +369,7 @@ const UpdateProjectInput = inputObjectType({ definition(t) { t.int('id') t.nonNull.string('title'); + t.nonNull.string('hashtag'); t.nonNull.string('website'); t.nonNull.string('tagline'); t.nonNull.string('description'); @@ -382,7 +391,9 @@ const UpdateProjectInput = inputObjectType({ type: TeamMemberInput }); t.nonNull.list.nonNull.int('recruit_roles'); // ids - t.nonNull.string('launch_status'); // "wip" | "launched" + t.nonNull.field('launch_status', { + type: ProjectLaunchStatusEnum + }); t.nonNull.list.nonNull.int('tournaments'); // ids } }) diff --git a/src/features/Projects/pages/ListProjectPage/Components/ExtrasTab/ExtrasTab.tsx b/src/features/Projects/pages/ListProjectPage/Components/ExtrasTab/ExtrasTab.tsx index 178ac90..18deacc 100644 --- a/src/features/Projects/pages/ListProjectPage/Components/ExtrasTab/ExtrasTab.tsx +++ b/src/features/Projects/pages/ListProjectPage/Components/ExtrasTab/ExtrasTab.tsx @@ -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(); + const { register, formState: { errors, }, control } = useFormContext(); - // usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) - - return (
@@ -25,7 +23,7 @@ export default function ExtrasTab(props: Props) { {...register("launch_status")} type="radio" name="launch_status" - value='wip' + value={ProjectLaunchStatusEnum.Wip} />

WIP 🛠️

@@ -37,7 +35,7 @@ export default function ExtrasTab(props: Props) { {...register("launch_status")} type="radio" name="launch_status" - value='launched' + value={ProjectLaunchStatusEnum.Launched} />

Launched 🚀

diff --git a/src/features/Projects/pages/ListProjectPage/Components/FormContainer/FormContainer.tsx b/src/features/Projects/pages/ListProjectPage/Components/FormContainer/FormContainer.tsx index add3831..d969109 100644 --- a/src/features/Projects/pages/ListProjectPage/Components/FormContainer/FormContainer.tsx +++ b/src/features/Projects/pages/ListProjectPage/Components/FormContainer/FormContainer.tsx @@ -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 -// screenshots: NestedValue - -// members: NestedValue<{ -// id: number, -// name: string, -// jobTitle: string | null, -// avatar: string, -// role: Team_Member_Role, -// }[]> -// recruit_roles: NestedValue - - -// launch_status: "wip" | "launched" -// tournaments: NestedValue -// } - - - export type IListProjectForm = Override recruit_roles: NestedValue tournaments: NestedValue + cover_image: NestedValue + thumbnail_image: NestedValue }> const schema: yup.SchemaOf = 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) { const methods = useForm({ 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) { screenshots: [], members: [], recruit_roles: [], - launch_status: 'wip', + launch_status: ProjectLaunchStatusEnum.Wip, tournaments: [], }, resolver: yupResolver(schema) as Resolver, diff --git a/src/features/Projects/pages/ListProjectPage/Components/ProjectDetailsTab/ProjectDetailsTab.tsx b/src/features/Projects/pages/ListProjectPage/Components/ProjectDetailsTab/ProjectDetailsTab.tsx index 11a2dce..7f42a90 100644 --- a/src/features/Projects/pages/ListProjectPage/Components/ProjectDetailsTab/ProjectDetailsTab.tsx +++ b/src/features/Projects/pages/ListProjectPage/Components/ProjectDetailsTab/ProjectDetailsTab.tsx @@ -15,11 +15,6 @@ export default function ProjectDetailsTab(props: Props) { const { register, formState: { errors, }, control, getValues } = useFormContext(); - - // usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) - - - return (
@@ -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) { />
+
+ {(errors.cover_image || errors.thumbnail_image) &&
+ {errors.cover_image &&

+ {errors.cover_image.message} +

} + {errors.thumbnail_image &&

+ {errors.thumbnail_image.message} +

} +
}

Project name*

@@ -110,7 +113,20 @@ export default function ProjectDetailsTab(props: Props) { {errors.description &&

{errors.description.message}

} - +

+ Hashtag* +

+
+ +
+ {errors.hashtag &&

+ {errors.hashtag.message} +

}
@@ -203,7 +219,7 @@ export default function ProjectDetailsTab(props: Props) {

📷 Screenshots

-

Choose up to 4 images from your project

+

Choose up to 4 screenshots from your project

(); + const { handleSubmit, formState: { isDirty, }, reset, getValues, watch } = useFormContext(); 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',]) diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index 1098ea4..2e364f0 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -109,8 +109,9 @@ export type CreateProjectInput = { description: Scalars['String']; discord?: InputMaybe; github?: InputMaybe; + hashtag: Scalars['String']; id?: InputMaybe; - launch_status: Scalars['String']; + launch_status: ProjectLaunchStatusEnum; members: Array; recruit_roles: Array; screenshots: Array; @@ -404,6 +405,11 @@ export type Project = { website: Scalars['String']; }; +export enum ProjectLaunchStatusEnum { + Launched = 'Launched', + Wip = 'WIP' +} + export type Query = { __typename?: 'Query'; allCategories: Array; @@ -719,8 +725,9 @@ export type UpdateProjectInput = { description: Scalars['String']; discord?: InputMaybe; github?: InputMaybe; + hashtag: Scalars['String']; id?: InputMaybe; - launch_status: Scalars['String']; + launch_status: ProjectLaunchStatusEnum; members: Array; recruit_roles: Array; screenshots: Array;