diff --git a/package.json b/package.json index bac9fdac..622c5bb6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react": "^18", "react-circular-progressbar": "^2.1.0", "react-dom": "^18", + "react-dropzone": "^14.3.5", "react-hook-form": "^7.53.0", "react-input-mask": "^2.0.4", "react-phone-number-input": "^3.4.5", diff --git a/src/components/ItemActions.tsx b/src/components/ItemActions.tsx index e756b124..f147a8c3 100644 --- a/src/components/ItemActions.tsx +++ b/src/components/ItemActions.tsx @@ -22,13 +22,13 @@ export function ItemActions({ item }: { item: IProject | ICompany }) { const { company, ...project } = item; setModal( , - 'editProject' + 'editProjectForm' ); } else { const { projects, ...company } = item; setModal( , - 'editCompany' + 'editCompanyForm' ); } } @@ -51,13 +51,17 @@ export function ItemActions({ item }: { item: IProject | ICompany }) { onClick={handleEdit} className="relative px-3 py-2 transition-opacity bg-[#37393B99] backdrop-blur-sm rounded-full bg-opacity-60 hover:bg-opacity-70" > - } className="w-4 h-4" /> + + + ) diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index a4701c19..eb3bcc13 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -7,7 +7,13 @@ import { getExampleNumber } from 'libphonenumber-js'; import examples from 'libphonenumber-js/mobile/examples'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; -import { useForm, UseFormGetValues, UseFormSetValue } from 'react-hook-form'; +import { + FieldValues, + Path, + useForm, + UseFormGetValues, + UseFormSetValue, +} from 'react-hook-form'; import ReactInputMask from 'react-input-mask'; import { Country } from 'react-phone-number-input'; import { SelectPhoneCode } from './SelectPhoneCode'; @@ -67,6 +73,7 @@ export function FeedbackForm() { @@ -127,32 +134,36 @@ export function FeedbackForm() { ); } -export function ProductOption({ +export function ProductOption({ text, setValue, getValues, + fieldName, }: { text: Product; - setValue: UseFormSetValue; - getValues: UseFormGetValues; + setValue: UseFormSetValue; + getValues: UseFormGetValues; + fieldName: Path; }) { const [chosen, setChosen] = useState(false); useEffect( () => setValue( - 'products', + fieldName, chosen - ? [...getValues('products'), text] - : getValues('products').filter((product: string) => product !== text) + ? [...getValues(fieldName), text] + : getValues(fieldName).filter((product: string) => product !== text) ), - [chosen, getValues, setValue, text] + [chosen, fieldName, getValues, setValue, text] ); return (
setChosen(!chosen)} > diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index 6cdf3ddc..33c0da44 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -18,10 +18,9 @@ export function Footer() {

Позвонить

8 800 770 00 67 - } - /> + + +
Написать

info@graff.tech - } - /> + + +
@@ -83,10 +81,9 @@ export function ContactLink({ href={href} className={`rounded-2xl bg-[#37393B99] p-[18px] hover:bg-white transition-colors hover:text-black flex justify-center w-full ${className}`} > - + + {children} + ); } diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 64d877e2..94d624a9 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -61,10 +61,9 @@ export function Header() { return (
- } - className="max-lg:hidden" - /> + + +
@@ -85,7 +84,9 @@ export function Header() { href={'/'} className="aspect-square p-4 lg:hidden hover:bg-[#232425] rounded-xl content-center" > - } className={'w-4 h-4'} /> + + +
{auth && ( @@ -212,10 +212,9 @@ export function Header() {

Смотреть кейс

) : ( - } - className="max-lg:hidden" - /> + + + )}
); diff --git a/src/components/Layout/ModalContainer.tsx b/src/components/Layout/ModalContainer.tsx index 0c86962c..5b6179e9 100644 --- a/src/components/Layout/ModalContainer.tsx +++ b/src/components/Layout/ModalContainer.tsx @@ -19,11 +19,12 @@ export function ModalContainer() { modal && (
{modal} diff --git a/src/components/Layout/SelectPhoneCode.tsx b/src/components/Layout/SelectPhoneCode.tsx index b2b229c7..f4d89f0f 100644 --- a/src/components/Layout/SelectPhoneCode.tsx +++ b/src/components/Layout/SelectPhoneCode.tsx @@ -49,10 +49,9 @@ export function SelectPhoneCode({ sizes="" />

{currentPhoneCode}

- : } - /> + + {open ? : } + {open && (
diff --git a/src/components/MediaUploader.tsx b/src/components/MediaUploader.tsx index 260fac21..fade19fd 100644 --- a/src/components/MediaUploader.tsx +++ b/src/components/MediaUploader.tsx @@ -1,7 +1,13 @@ import { api } from '@/api'; +import { ClassNameWrapper } from '@/hocs/ClassNameWrapper'; +import { Button } from '@/ui/Button'; import Image from 'next/image'; -import { ChangeEvent, useCallback, useEffect, useState } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import Dropzone from 'react-dropzone'; import { FieldValues, Path, PathValue, UseFormSetValue } from 'react-hook-form'; +import { PlusIcon } from './icons/PlusIcon'; +import { RestartIcon } from './icons/RestartIcon'; +import { TrashIcon } from './icons/TrashIcon'; export function MediaUploader({ dest, @@ -9,9 +15,7 @@ export function MediaUploader({ fieldName, item, label, - className, - width = 300, - height = 300, + className = '', }: { dest: string; setValue: UseFormSetValue; @@ -19,8 +23,6 @@ export function MediaUploader({ item: Record<'img', string> & Record<'id', string>; label: string; className?: string; - width?: number; - height?: number; }) { const [file, setFile] = useState(); const [previewFile, setPreviewFile] = useState(''); @@ -34,7 +36,7 @@ export function MediaUploader({ setPreviewFile(URL.createObjectURL(targetFile)); } - const uploadFile = useCallback(async () => { + async function uploadFile() { if (!file) return; const formData = new FormData(); @@ -51,36 +53,97 @@ export function MediaUploader({ alert(`Error: ${error.message}`); } } - }, [dest, fieldName, file, setValue]); + } useEffect(() => { uploadFile(); }, [file, uploadFile]); + const ref = useRef(null); + return ( - + ); } diff --git a/src/components/icons/RestartIcon.tsx b/src/components/icons/RestartIcon.tsx new file mode 100644 index 00000000..0a219fa0 --- /dev/null +++ b/src/components/icons/RestartIcon.tsx @@ -0,0 +1,19 @@ +export function RestartIcon() { + return ( + + + + + + + + + + + ); +} diff --git a/src/components/modals/ArticleContentEditorModal.tsx b/src/components/modals/ArticleContentEditorModal.tsx index b7a190ee..f82268c5 100644 --- a/src/components/modals/ArticleContentEditorModal.tsx +++ b/src/components/modals/ArticleContentEditorModal.tsx @@ -41,7 +41,7 @@ export function ArticleContentEditorModal({ data.source + '" muted="true" autoplay="true" loop="true" playsinline style="aspect-ratio: 16/9; width: 768px">', images_upload_credentials: true, - images_upload_handler: async blobInfo => { + images_upload_handler: async (blobInfo) => { const formData = new FormData(); formData.append('files', blobInfo.blob(), blobInfo.filename()); formData.append('dest', 'blog'); @@ -51,7 +51,7 @@ export function ArticleContentEditorModal({ return process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0]; }, file_picker_types: 'image media', - file_picker_callback: async cb => { + file_picker_callback: async (cb) => { videoUploadRef.current!.onchange = async function () { const reader = new FileReader(); const file = videoUploadRef.current?.files?.[0]; @@ -65,7 +65,7 @@ export function ArticleContentEditorModal({ formData.append( 'files', blobInfo?.blob()!, - blobInfo?.filename(), + blobInfo?.filename() ); formData.append('dest', 'blog'); const res = await api @@ -120,13 +120,15 @@ export function ArticleContentEditorModal({ ref={videoUploadRef} />
); diff --git a/src/components/modals/CompanyFormModal.tsx b/src/components/modals/CompanyFormModal.tsx index e11beb89..62f01e38 100644 --- a/src/components/modals/CompanyFormModal.tsx +++ b/src/components/modals/CompanyFormModal.tsx @@ -27,7 +27,6 @@ export function CompanyFormModal({ action, defaultValues, }: ICompanyFormModalProps) { - console.log(defaultValues); const { setModal } = useModalStore(); const { register, handleSubmit, setValue } = useForm({ @@ -48,7 +47,7 @@ export function CompanyFormModal({ }); return ( -
+

{action === 'create' ? 'Добавление' : 'Редактирование'} компании diff --git a/src/components/modals/ModalWithFeedbackForm.tsx b/src/components/modals/ModalWithFeedbackForm.tsx index 479386c7..e31ff06e 100644 --- a/src/components/modals/ModalWithFeedbackForm.tsx +++ b/src/components/modals/ModalWithFeedbackForm.tsx @@ -40,7 +40,6 @@ export function ModalWithFeedbackForm() { function handleSubmit(e: FormEvent) { e.preventDefault(); - sendMail(); } @@ -192,15 +191,13 @@ export function ModalWithFeedbackForm() { className="py-5 px-6 mt-[213px]" icon={ isLoading ? ( - } - className="relative w-6 h-6 animate-spin" - /> + + + ) : ( - } - className="relative w-6 h-6" - /> + + + ) } > @@ -229,10 +226,9 @@ export function ModalWithFeedbackForm() { width="full" className="py-5 px-6 absolute top-[50vh]" icon={ - } - /> + + + } onClick={() => setModal(false, '')} > diff --git a/src/components/modals/ProjectFormModal.tsx b/src/components/modals/ProjectFormModal.tsx index 110bbc8e..72220532 100644 --- a/src/components/modals/ProjectFormModal.tsx +++ b/src/components/modals/ProjectFormModal.tsx @@ -2,14 +2,22 @@ import { api } from '@/api'; import { projectsTags } from '@/consts/projectsTags'; +import { AddItemWrapper } from '@/hocs/AddItemWrapper'; +import { ClassNameWrapper } from '@/hocs/ClassNameWrapper'; import { useGetCompaniesQuery } from '@/queries/getCompanies'; import { useModalStore } from '@/stores/useModalStore'; import { IProject } from '@/types/IProject'; +import { Product } from '@/types/Product'; import { Button } from '@/ui/Button'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { CloseIcon } from '../icons/CloseIcon'; +import { useEffect, useRef } from 'react'; +import { SubmitHandler, useForm, useWatch } from 'react-hook-form'; +import { ProductOption } from '../Layout/Feedback'; import { MediaUploader } from '../MediaUploader'; +import { CheckIcon } from '../icons/CheckIcon'; +import { CloseIcon } from '../icons/CloseIcon'; +import { PlusIcon } from '../icons/PlusIcon'; +import { CompanyFormModal } from './CompanyFormModal'; export interface IProjectFormInput { title: string; @@ -19,7 +27,7 @@ export interface IProjectFormInput { image: string; stage: number; releaseDate: string; - tags: string[]; + tags: Product[]; } interface IProjectFormModalProps { @@ -52,147 +60,203 @@ export function ProjectFormModal({ }, }); - const { register, handleSubmit, setValue } = useForm({ - defaultValues: - action === 'create' - ? { - tags: [], - stage: 1, - releaseDate: new Date().toISOString().split('T')[0], - } - : { - ...defaultValues, - releaseDate: defaultValues?.releaseDate.split('T')[0], - }, - }); + const { register, handleSubmit, setValue, getValues, control } = + useForm({ + defaultValues: + action === 'create' + ? { + tags: [], + stage: 1, + releaseDate: new Date().toISOString().split('T')[0], + } + : { + ...defaultValues, + releaseDate: defaultValues?.releaseDate.split('T')[0], + }, + }); + + const textAreaRef = useRef(null); + + const description = useWatch({ control }); + + useEffect(() => { + if (textAreaRef.current) { + textAreaRef.current.style.height = 'auto'; + textAreaRef.current.style.height = + textAreaRef.current.scrollHeight + 'px'; + } + }, [textAreaRef, description]); + + const { ref, ...descriptionRegister } = register('description'); return ( -

-
-

- {action === 'create' ? 'Создание проекта' : 'Изменение проекта'} -

+
+
+
)} - className="space-y-4" + className="space-y-10" > -
-
- - +
+ + +
+
+ + +
+
+ +
+ {(projectsTags as Product[]).map((option, index) => ( + + ))}
-
- - -
-
- - -
-
- -