mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-27 02:04:24 +01:00
feat: add images input components to list-project, connect with api
This commit is contained in:
@@ -34,7 +34,7 @@ export default function CoverImageInput(props: Props) {
|
||||
wrapperClass='h-full'
|
||||
render={({ img, isUploading, isDraggingOnWindow }) =>
|
||||
<div className="w-full h-full group relative ">
|
||||
{!img && <div className='w-full h-full flex flex-col justify-center items-center bg-gray-500 outline outline-2 outline-gray-200'>
|
||||
{!img && <div className={`w-full h-full flex flex-col justify-center items-center bg-gray-500 outline outline-2 outline-gray-200 ${props.rounded ?? 'rounded-12'}`}>
|
||||
<p className="text-center text-gray-100 text-body1 md:text-h1 mb-8"><FaImage /></p>
|
||||
<div className={`text-gray-100 text-center text-body4`}>
|
||||
Drop a <span className="font-bold">COVER IMAGE</span> here or <br /> <span className="text-blue-300 underline">Click to browse</span>
|
||||
@@ -56,7 +56,7 @@ export default function CoverImageInput(props: Props) {
|
||||
</>}
|
||||
{isUploading &&
|
||||
<div
|
||||
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||
className={`absolute inset-0 bg-gray-400 ${props.rounded ?? 'rounded-12'} bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform`}
|
||||
>
|
||||
<RotatingLines
|
||||
strokeColor="#fff"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ScreenshotsInput, { ScreenshotType } from './ScreenshotsInput';
|
||||
import ScreenshotsInput from './ScreenshotsInput';
|
||||
import { WrapForm, WrapFormController } from 'src/utils/storybook/decorators';
|
||||
import { ImageInput } from 'src/graphql';
|
||||
|
||||
export default {
|
||||
title: 'Shared/Inputs/Files Inputs/Screenshots',
|
||||
component: ScreenshotsInput,
|
||||
decorators: [
|
||||
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||
WrapFormController<{ screenshots: Array<ImageInput> }>({
|
||||
logValues: true,
|
||||
name: "screenshots",
|
||||
defaultValues: {
|
||||
@@ -29,7 +30,7 @@ Empty.args = {
|
||||
|
||||
export const WithValues = Template.bind({});
|
||||
WithValues.decorators = [
|
||||
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||
WrapFormController<{ screenshots: Array<ImageInput> }>({
|
||||
logValues: true,
|
||||
name: "screenshots",
|
||||
defaultValues: {
|
||||
@@ -51,7 +52,7 @@ WithValues.args = {
|
||||
|
||||
export const Full = Template.bind({});
|
||||
Full.decorators = [
|
||||
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||
WrapFormController<{ screenshots: Array<ImageInput> }>({
|
||||
logValues: true,
|
||||
name: "screenshots",
|
||||
defaultValues: {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { getMockSenderEnhancer } from "@rpldy/mock-sender";
|
||||
import ScreenshotThumbnail from "./ScreenshotThumbnail";
|
||||
import { FiCamera } from "react-icons/fi";
|
||||
import { Control, Path, useController } from "react-hook-form";
|
||||
import { ImageInput } from "src/graphql";
|
||||
import { fetchUploadImageUrl } from "src/api/uploading";
|
||||
|
||||
|
||||
|
||||
@@ -20,15 +22,10 @@ const mockSenderEnhancer = getMockSenderEnhancer({
|
||||
|
||||
const MAX_UPLOAD_COUNT = 4 as const;
|
||||
|
||||
export interface ScreenshotType {
|
||||
id: string,
|
||||
name: string,
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: ScreenshotType[],
|
||||
onChange: (new_value: ScreenshotType[]) => void
|
||||
value: ImageInput[],
|
||||
onChange: (new_value: ImageInput[]) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +46,6 @@ export default function ScreenshotsInput(props: Props) {
|
||||
multiple={true}
|
||||
inputFieldName='file'
|
||||
grouped={false}
|
||||
enhancer={mockSenderEnhancer}
|
||||
listeners={{
|
||||
[UPLOADER_EVENTS.BATCH_ADD]: (batch) => {
|
||||
setUploadingCount(v => v + batch.items.length)
|
||||
@@ -96,17 +92,18 @@ const DropZone = forwardRef<any, any>((props, ref) => {
|
||||
|
||||
|
||||
useRequestPreSend(async (data) => {
|
||||
|
||||
const filename = data.items?.[0].file.name ?? ''
|
||||
|
||||
// const url = await fetchUploadUrl({ filename });
|
||||
const res = await fetchUploadImageUrl({ filename });
|
||||
|
||||
return {
|
||||
options: {
|
||||
destination: {
|
||||
url: "URL"
|
||||
}
|
||||
url: res.uploadURL
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const onZoneClick = useCallback(
|
||||
|
||||
@@ -71,11 +71,11 @@ const schema: yup.SchemaOf<IListProjectForm> = yup.object({
|
||||
github: yup.string().url().ensure(),
|
||||
category_id: yup.number().required("Please choose a category"),
|
||||
capabilities: yup.array().of(yup.string().required()).default([]),
|
||||
screenshots: 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.object() as any).default([]),
|
||||
recruit_roles: yup.array().of(yup.number().required()).default([]),
|
||||
launch_status: yup.mixed().oneOf(['wip', 'launched']).default('wip'),
|
||||
tournaments: yup.array().default([])
|
||||
tournaments: yup.array().of(yup.number().required()).default([])
|
||||
}).required();
|
||||
|
||||
export default function FormContainer(props: PropsWithChildren<Props>) {
|
||||
|
||||
@@ -5,6 +5,9 @@ import { FiCamera, FiGithub, FiTwitter } from "react-icons/fi";
|
||||
import CategoriesInput from "../CategoriesInput/CategoriesInput";
|
||||
import CapabilitiesInput from "../CapabilitiesInput/CapabilitiesInput";
|
||||
import { IListProjectForm } from "../FormContainer/FormContainer";
|
||||
import AvatarInput from "src/Components/Inputs/FilesInputs/AvatarInput/AvatarInput";
|
||||
import CoverImageInput from "src/Components/Inputs/FilesInputs/CoverImageInput/CoverImageInput";
|
||||
import ScreenshotsInput from "src/Components/Inputs/FilesInputs/ScreenshotsInput/ScreenshotsInput";
|
||||
|
||||
interface Props { }
|
||||
|
||||
@@ -21,15 +24,28 @@ export default function ProjectDetailsTab(props: Props) {
|
||||
<div className="md:col-span-2 flex flex-col gap-24">
|
||||
<Card className="" defaultPadding={false}>
|
||||
<div className="bg-gray-600 relative h-[160px] rounded-t-12 md:rounded-t-16">
|
||||
<Controller
|
||||
control={control}
|
||||
name="cover_image"
|
||||
render={({ field: { onChange, value, onBlur, ref } }) => <CoverImageInput
|
||||
value={value}
|
||||
rounded='rounded-t-12 md:rounded-t-16'
|
||||
onChange={e => {
|
||||
onChange(e)
|
||||
}}
|
||||
// uploadText='Add a cover image'
|
||||
/>
|
||||
|
||||
}
|
||||
/>
|
||||
<div className="absolute left-24 bottom-0 translate-y-1/2">
|
||||
{/* <Avatar src={data.avatar} width={120} /> */}
|
||||
<div
|
||||
className="rounded-full w-[120px] aspect-square border-2 border-gray-200 bg-white flex flex-col gap-8 items-center justify-center"
|
||||
role={'button'}
|
||||
>
|
||||
<FiCamera className="text-gray-400 text-h2" />
|
||||
<span className="text-gray-400 text-body6">Add image</span>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="thumbnail_image"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<AvatarInput value={value} onChange={onChange} width={120} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-16 md:p-24 mt-64">
|
||||
@@ -184,6 +200,26 @@ export default function ProjectDetailsTab(props: Props) {
|
||||
{errors.capabilities && <p className='input-error'>{errors.capabilities?.message}</p>}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<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>
|
||||
<div className="mt-24">
|
||||
<Controller
|
||||
control={control}
|
||||
name="screenshots"
|
||||
render={({ field: { onChange, value, onBlur, ref } }) => <ScreenshotsInput
|
||||
value={value}
|
||||
onChange={e => {
|
||||
onChange(e)
|
||||
}}
|
||||
/>
|
||||
|
||||
}
|
||||
/>
|
||||
{errors.capabilities && <p className='input-error'>{errors.capabilities?.message}</p>}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,31 +9,40 @@ import { tabs } from '../../ListProjectPage'
|
||||
import { NotificationsService } from 'src/services'
|
||||
import { useAppDispatch } from 'src/utils/hooks';
|
||||
import { openModal } from 'src/redux/features/modals.slice';
|
||||
import { useCreateProjectMutation, useUpdateProjectMutation } from 'src/graphql'
|
||||
|
||||
interface Props {
|
||||
currentTab: keyof typeof tabs
|
||||
onNext: () => void
|
||||
onBackToFirstPage: () => void
|
||||
}
|
||||
|
||||
export default function SaveChangesCard(props: Props) {
|
||||
|
||||
const { handleSubmit, formState: { errors, isDirty, }, reset, getValues, watch } = useFormContext<IListProjectForm>();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isUpdating = useMemo(() => !!getValues('id'), [getValues]);
|
||||
|
||||
const [update, updatingStatus] = useUpdateProjectMutation();
|
||||
const [create, creatingStatus] = useCreateProjectMutation()
|
||||
|
||||
const [img, name, tagline] = watch(['thumbnail_image', 'title', 'tagline'])
|
||||
|
||||
const [img, name, tagline] = watch(['thumbnail_image', 'title', 'tagline',])
|
||||
|
||||
const clickCancel = () => {
|
||||
if (window.confirm('You might lose some unsaved changes. Are you sure you want to continue?'))
|
||||
reset();
|
||||
}
|
||||
|
||||
const clickSubmit = handleSubmit<IListProjectForm>(data => {
|
||||
|
||||
const clickSubmit = handleSubmit<IListProjectForm>(async data => {
|
||||
try {
|
||||
await (isUpdating ? update({ variables: { input: data } }) : create({ variables: { input: data } }))
|
||||
} catch (error) {
|
||||
NotificationsService.error("A network error happened...");
|
||||
return;
|
||||
}
|
||||
if (isUpdating)
|
||||
NotificationsService.success("Saved changes successfully")
|
||||
else {
|
||||
@@ -48,10 +57,9 @@ export default function SaveChangesCard(props: Props) {
|
||||
}
|
||||
}))
|
||||
}
|
||||
console.log(data);
|
||||
}, () => {
|
||||
}, (errors) => {
|
||||
NotificationsService.error("Please fill all the required fields");
|
||||
navigate(tabs['project-details'].path)
|
||||
props.onBackToFirstPage()
|
||||
})
|
||||
|
||||
|
||||
@@ -91,7 +99,7 @@ export default function SaveChangesCard(props: Props) {
|
||||
>
|
||||
List your product
|
||||
</Button>
|
||||
}, [clickSubmit, isDirty, isLoading, isUpdating, props.currentTab])
|
||||
}, [clickSubmit, isDirty, isLoading, isUpdating, props.currentTab, props.onNext])
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -117,6 +117,7 @@ export default function ListProjectPage() {
|
||||
if (curTab === 'project-details') setCurTab(tabs['team'].path)
|
||||
else if (curTab === 'team') setCurTab(tabs['extras'].path)
|
||||
}}
|
||||
onBackToFirstPage={() => setCurTab(tabs["project-details"].path)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user