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

@@ -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

View File

@@ -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!]!

View File

@@ -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
}
})

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',])

View File

@@ -109,8 +109,9 @@ export type CreateProjectInput = {
description: Scalars['String'];
discord?: InputMaybe<Scalars['String']>;
github?: InputMaybe<Scalars['String']>;
hashtag: Scalars['String'];
id?: InputMaybe<Scalars['Int']>;
launch_status: Scalars['String'];
launch_status: ProjectLaunchStatusEnum;
members: Array<TeamMemberInput>;
recruit_roles: Array<Scalars['Int']>;
screenshots: Array<ImageInput>;
@@ -404,6 +405,11 @@ export type Project = {
website: Scalars['String'];
};
export enum ProjectLaunchStatusEnum {
Launched = 'Launched',
Wip = 'WIP'
}
export type Query = {
__typename?: 'Query';
allCategories: Array<Category>;
@@ -719,8 +725,9 @@ export type UpdateProjectInput = {
description: Scalars['String'];
discord?: InputMaybe<Scalars['String']>;
github?: InputMaybe<Scalars['String']>;
hashtag: Scalars['String'];
id?: InputMaybe<Scalars['Int']>;
launch_status: Scalars['String'];
launch_status: ProjectLaunchStatusEnum;
members: Array<TeamMemberInput>;
recruit_roles: Array<Scalars['Int']>;
screenshots: Array<ImageInput>;