mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-28 10:44:23 +01:00
feat: add "hashtag" field to api, add images validation to FE, make launch_status an enum field
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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',])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user