upd
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -22,13 +22,13 @@ export function ItemActions({ item }: { item: IProject | ICompany }) {
|
||||
const { company, ...project } = item;
|
||||
setModal(
|
||||
<ProjectFormModal action="edit" defaultValues={project} />,
|
||||
'editProject'
|
||||
'editProjectForm'
|
||||
);
|
||||
} else {
|
||||
const { projects, ...company } = item;
|
||||
setModal(
|
||||
<CompanyFormModal action="edit" defaultValues={company} />,
|
||||
'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"
|
||||
>
|
||||
<ClassNameWrapper element={<EditIcon />} className="w-4 h-4" />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<EditIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="relative px-3 py-2 transition-opacity bg-[#37393B99] backdrop-blur-sm rounded-full bg-opacity-60 hover:bg-opacity-70"
|
||||
>
|
||||
<ClassNameWrapper element={<DeleteIcon />} className="w-4 h-4" />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<DeleteIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
<ProductOption
|
||||
key={index}
|
||||
text={option}
|
||||
fieldName="products"
|
||||
setValue={setValue}
|
||||
getValues={getValues}
|
||||
/>
|
||||
@@ -127,32 +134,36 @@ export function FeedbackForm() {
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductOption({
|
||||
export function ProductOption<T extends FieldValues>({
|
||||
text,
|
||||
setValue,
|
||||
getValues,
|
||||
fieldName,
|
||||
}: {
|
||||
text: Product;
|
||||
setValue: UseFormSetValue<IInput>;
|
||||
getValues: UseFormGetValues<IInput>;
|
||||
setValue: UseFormSetValue<T>;
|
||||
getValues: UseFormGetValues<T>;
|
||||
fieldName: Path<T>;
|
||||
}) {
|
||||
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 (
|
||||
<div
|
||||
className={`cursor-pointer px-6 py-[17px] transition-colors rounded-2xl font-medium btnm select-none ${
|
||||
className={`cursor-pointer transition-colors rounded-2xl font-medium select-none ${
|
||||
chosen ? 'bg-white text-black' : 'bg-[#37393B99]'
|
||||
} ${
|
||||
fieldName === 'products' ? 'px-6 py-[17px] btnm' : 'px-3 py-[9px] btns'
|
||||
}`}
|
||||
onClick={() => setChosen(!chosen)}
|
||||
>
|
||||
|
||||
@@ -18,10 +18,9 @@ export function Footer() {
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Позвонить</p>
|
||||
<div className="lg:line2 sm:heading1 line2 flex items-center font-medium">
|
||||
8 800 770 00 67
|
||||
<ClassNameWrapper
|
||||
className="lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9"
|
||||
element={<ArrowMoreIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9">
|
||||
<ArrowMoreIcon />
|
||||
</ClassNameWrapper>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
@@ -31,10 +30,9 @@ export function Footer() {
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Написать</p>
|
||||
<div className="lg:line2 sm:heading1 line2 flex items-center font-medium">
|
||||
info@graff.tech
|
||||
<ClassNameWrapper
|
||||
className="lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9"
|
||||
element={<ArrowMoreIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9">
|
||||
<ArrowMoreIcon />
|
||||
</ClassNameWrapper>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="gap-y-2 justify-stretch sm:gap-x-2 gap-x-1 sm:flex-col flex">
|
||||
@@ -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}`}
|
||||
>
|
||||
<ClassNameWrapper
|
||||
className="lg:w-5 lg:h-5 sm:w-4 sm:h-4 w-5 h-5"
|
||||
element={children}
|
||||
/>
|
||||
<ClassNameWrapper className="lg:w-5 lg:h-5 sm:w-4 sm:h-4 w-5 h-5">
|
||||
{children}
|
||||
</ClassNameWrapper>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,10 +61,9 @@ export function Header() {
|
||||
return (
|
||||
<header className="lg:mt-5 relative flex items-center px-5">
|
||||
<Link href={'/'}>
|
||||
<ClassNameWrapper
|
||||
element={<LogoWithTextIcon />}
|
||||
className="max-lg:hidden"
|
||||
/>
|
||||
<ClassNameWrapper className="max-lg:hidden">
|
||||
<LogoWithTextIcon />
|
||||
</ClassNameWrapper>
|
||||
</Link>
|
||||
<div className="relative flex justify-center flex-1 m-auto">
|
||||
<AnimatePresence>
|
||||
@@ -85,7 +84,9 @@ export function Header() {
|
||||
href={'/'}
|
||||
className="aspect-square p-4 lg:hidden hover:bg-[#232425] rounded-xl content-center"
|
||||
>
|
||||
<ClassNameWrapper element={<LogoIcon />} className={'w-4 h-4'} />
|
||||
<ClassNameWrapper className={'w-4 h-4'}>
|
||||
<LogoIcon />
|
||||
</ClassNameWrapper>
|
||||
</Link>
|
||||
<div ref={productsBtnRef} className="max-md:hidden">
|
||||
<button
|
||||
@@ -126,10 +127,9 @@ export function Header() {
|
||||
className="!border-none p-[18px] hover:bg-[#232425] rounded-2xl active:opacity-50 outline-none"
|
||||
onClick={() => setBurgerOpened((prev) => !prev)}
|
||||
>
|
||||
<ClassNameWrapper
|
||||
element={burgerOpened ? <CloseIcon /> : <BurgerIcon />}
|
||||
className="w-4 h-4 text-white"
|
||||
/>
|
||||
<ClassNameWrapper className="w-4 h-4 text-white">
|
||||
{burgerOpened ? <CloseIcon /> : <BurgerIcon />}
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
{auth && (
|
||||
@@ -212,10 +212,9 @@ export function Header() {
|
||||
<p className="btnm font-medium">Смотреть кейс</p>
|
||||
</Link>
|
||||
) : (
|
||||
<ClassNameWrapper
|
||||
element={<SkolkovoIcon />}
|
||||
className="max-lg:hidden"
|
||||
/>
|
||||
<ClassNameWrapper className="max-lg:hidden">
|
||||
<SkolkovoIcon />
|
||||
</ClassNameWrapper>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -19,11 +19,12 @@ export function ModalContainer() {
|
||||
modal && (
|
||||
<div
|
||||
className={
|
||||
'fixed left-0 z-[11] w-full h-full flex justify-center items-start transition-opacity' +
|
||||
'fixed left-0 z-[14] w-full h-full flex justify-center items-start transition-opacity' +
|
||||
(name === 'video' || name === 'form'
|
||||
? ' bg-black bg-opacity-90 [backdrop-filter:blur(10px);]'
|
||||
: '') +
|
||||
(name === 'menu' ? ' lg:top-16 top-12' : ' top-0')
|
||||
(name === 'menu' ? ' lg:top-16 top-12' : ' top-0') +
|
||||
(name.endsWith('Form') ? ' overflow-auto' : '')
|
||||
}
|
||||
>
|
||||
{modal}
|
||||
|
||||
@@ -49,10 +49,9 @@ export function SelectPhoneCode({
|
||||
sizes=""
|
||||
/>
|
||||
<p className="btnl font-medium">{currentPhoneCode}</p>
|
||||
<ClassNameWrapper
|
||||
className="max-sm:w-4 sm:max-lg:w-5 flex-1"
|
||||
element={open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="max-sm:w-4 sm:max-lg:w-5 flex-1">
|
||||
{open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="space-y-1 absolute z-10 bg-[#14161F] top-[100%] -left-1 border border-t-0 rounded-b-lg border-[#37393B] max-h-[300px] overflow-y-auto overflow-x-hidden">
|
||||
|
||||
@@ -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<T extends FieldValues>({
|
||||
dest,
|
||||
@@ -9,9 +15,7 @@ export function MediaUploader<T extends FieldValues>({
|
||||
fieldName,
|
||||
item,
|
||||
label,
|
||||
className,
|
||||
width = 300,
|
||||
height = 300,
|
||||
className = '',
|
||||
}: {
|
||||
dest: string;
|
||||
setValue: UseFormSetValue<T>;
|
||||
@@ -19,8 +23,6 @@ export function MediaUploader<T extends FieldValues>({
|
||||
item: Record<'img', string> & Record<'id', string>;
|
||||
label: string;
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}) {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [previewFile, setPreviewFile] = useState('');
|
||||
@@ -34,7 +36,7 @@ export function MediaUploader<T extends FieldValues>({
|
||||
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<T extends FieldValues>({
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}, [dest, fieldName, file, setValue]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
uploadFile();
|
||||
}, [file, uploadFile]);
|
||||
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<label
|
||||
className={
|
||||
'relative border border-dashed border-neutral-500 px-3 py-2 cursor-pointer rounded-lg flex flex-col gap-2 ' +
|
||||
className
|
||||
}
|
||||
<Dropzone
|
||||
onDrop={([file]) => {
|
||||
setFile(file);
|
||||
setPreviewFile(URL.createObjectURL(file));
|
||||
}}
|
||||
noClick
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept={'image/*'}
|
||||
className="absolute opacity-0"
|
||||
onChange={handleChangeFile}
|
||||
/>
|
||||
<p className="truncate">{label}</p>
|
||||
{(previewFile || item.img) && (
|
||||
<Image
|
||||
src={previewFile || process.env.NEXT_PUBLIC_S3_BUCKET + item.img}
|
||||
width={300}
|
||||
height={300}
|
||||
alt={''}
|
||||
className="pointer-events-none"
|
||||
sizes=""
|
||||
/>
|
||||
{({ getRootProps, getInputProps, inputRef }) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`relative border border-[#37393B] aspect-[824/340] px-3 py-2 rounded-lg flex flex-col justify-center items-center gap-2${
|
||||
className ? ' ' + className : ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
ref={inputRef}
|
||||
tabIndex={0}
|
||||
accept={'image/*'}
|
||||
className=""
|
||||
onChange={handleChangeFile}
|
||||
{...getInputProps()}
|
||||
/>
|
||||
{previewFile || item.img ? (
|
||||
<>
|
||||
<Image
|
||||
src={
|
||||
previewFile || process.env.NEXT_PUBLIC_S3_BUCKET + item.img
|
||||
}
|
||||
alt={''}
|
||||
className="pointer-events-none aspect-square object-cover"
|
||||
fill
|
||||
sizes="calc(292/824*100%)"
|
||||
/>
|
||||
<div className="absolute left-6 top-6 flex gap-2">
|
||||
<Button
|
||||
onClick={() => inputRef.current?.click()}
|
||||
color="secondary"
|
||||
className="px-3 py-2 bg-[#37393B99]"
|
||||
rounded="xl"
|
||||
icon={
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<RestartIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
>
|
||||
Заменить
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setValue(fieldName, undefined!);
|
||||
setPreviewFile('');
|
||||
}}
|
||||
className="bg-[#37393B99] px-3 py-2 rounded-xl"
|
||||
>
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<TrashIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<p className="text-[#7A7A7A] font-medium text1 text-center max-w-[calc(346/824*100%)]">
|
||||
{label}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => inputRef.current?.click()}
|
||||
color="secondary"
|
||||
className="bg-[#37393B99] rounded-xl px-3 py-[9px]"
|
||||
icon={
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<PlusIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
>
|
||||
Выбрать
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export function RestartIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_2282_11898)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.66902 2.33908L10.976 1.55797L10.4268 0.638916L7.31283 2.49991L9.19665 5.41377L10.0958 4.83247L9.11258 3.31166C11.6606 3.81054 13.5953 6.09224 13.5953 8.84614C13.5953 11.9673 11.1101 14.482 8.06347 14.482C5.01685 14.482 2.53162 11.9673 2.53162 8.84614C2.53162 7.19267 3.22972 5.70784 4.34082 4.67735L3.61274 3.89232C2.29004 5.11907 1.46094 6.88552 1.46094 8.84614C1.46094 12.5414 4.40845 15.5527 8.06347 15.5527C11.7185 15.5527 14.666 12.5414 14.666 8.84614C14.666 5.71099 12.5444 3.06824 9.66902 2.33908Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2282_11898">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export function ArticleContentEditorModal({
|
||||
data.source +
|
||||
'" muted="true" autoplay="true" loop="true" playsinline style="aspect-ratio: 16/9; width: 768px"></video>',
|
||||
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}
|
||||
/>
|
||||
<button
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setModal(null, '');
|
||||
}}
|
||||
className="absolute top-4 right-4 z-[2]"
|
||||
>
|
||||
<ClassNameWrapper element={<CloseIcon />} className="text-black" />
|
||||
<ClassNameWrapper className="text-black">
|
||||
<CloseIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,6 @@ export function CompanyFormModal<TAction extends 'create' | 'edit'>({
|
||||
action,
|
||||
defaultValues,
|
||||
}: ICompanyFormModalProps<TAction>) {
|
||||
console.log(defaultValues);
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm<ICompanyFormInput>({
|
||||
@@ -48,7 +47,7 @@ export function CompanyFormModal<TAction extends 'create' | 'edit'>({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="text-black bg-white p-4 rounded-lg relative top-[100px] space-y-4">
|
||||
<div className="text-black bg-[#232425] p-4 rounded-lg relative top-[100px] space-y-4">
|
||||
<div className="flex justify-between items-center border-b border-[#ccc] pb-4 gap-4">
|
||||
<p className="text-xl">
|
||||
{action === 'create' ? 'Добавление' : 'Редактирование'} компании
|
||||
|
||||
@@ -40,7 +40,6 @@ export function ModalWithFeedbackForm() {
|
||||
|
||||
function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
|
||||
sendMail();
|
||||
}
|
||||
|
||||
@@ -192,15 +191,13 @@ export function ModalWithFeedbackForm() {
|
||||
className="py-5 px-6 mt-[213px]"
|
||||
icon={
|
||||
isLoading ? (
|
||||
<ClassNameWrapper
|
||||
element={<LoaderIcon />}
|
||||
className="relative w-6 h-6 animate-spin"
|
||||
/>
|
||||
<ClassNameWrapper className="relative w-6 h-6 animate-spin">
|
||||
<LoaderIcon />
|
||||
</ClassNameWrapper>
|
||||
) : (
|
||||
<ClassNameWrapper
|
||||
element={<MailIcon />}
|
||||
className="relative w-6 h-6"
|
||||
/>
|
||||
<ClassNameWrapper className="relative w-6 h-6">
|
||||
<MailIcon />
|
||||
</ClassNameWrapper>
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -229,10 +226,9 @@ export function ModalWithFeedbackForm() {
|
||||
width="full"
|
||||
className="py-5 px-6 absolute top-[50vh]"
|
||||
icon={
|
||||
<ClassNameWrapper
|
||||
className="w-6 h-6"
|
||||
element={<ArrowRightIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="w-6 h-6">
|
||||
<ArrowRightIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
onClick={() => setModal(false, '')}
|
||||
>
|
||||
|
||||
@@ -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<TAction extends 'create' | 'edit'> {
|
||||
@@ -52,147 +60,203 @@ export function ProjectFormModal<TAction extends 'create' | 'edit'>({
|
||||
},
|
||||
});
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm<IProjectFormInput>({
|
||||
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<IProjectFormInput>({
|
||||
defaultValues:
|
||||
action === 'create'
|
||||
? {
|
||||
tags: [],
|
||||
stage: 1,
|
||||
releaseDate: new Date().toISOString().split('T')[0],
|
||||
}
|
||||
: {
|
||||
...defaultValues,
|
||||
releaseDate: defaultValues?.releaseDate.split('T')[0],
|
||||
},
|
||||
});
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(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 (
|
||||
<div className="relative p-4 space-y-4 text-black bg-white rounded-lg top-10">
|
||||
<div className="flex justify-between items-center border-b border-[#ccc] pb-4 gap-4">
|
||||
<p className="text-xl">
|
||||
{action === 'create' ? 'Создание проекта' : 'Изменение проекта'}
|
||||
</p>
|
||||
<div className="relative py-10 space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] pl-[75px] pr-[55px] overflow-y-auto">
|
||||
<div className="absolute flex justify-between top-3 left-4 right-4">
|
||||
<button
|
||||
onClick={() => setModal(null, '')}
|
||||
className="p-2 transition-colors rounded-full hover:bg-white hover:bg-opacity-10"
|
||||
className="w-fit p-4"
|
||||
onClick={() => setModal(null, 'addProjectForm')}
|
||||
>
|
||||
<CloseIcon />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<CloseIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => setModal(null, 'addProjectForm')}
|
||||
icon={
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<CheckIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit(mutate as SubmitHandler<IProjectFormInput>)}
|
||||
className="space-y-4"
|
||||
className="space-y-10"
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name">Название</label>
|
||||
<input
|
||||
required
|
||||
autoFocus
|
||||
type="text"
|
||||
className="px-3 py-2 border rounded-lg outline-none border-neutral-500"
|
||||
placeholder="Название"
|
||||
{...register('title', { required: true })}
|
||||
id="name"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="title" className="heading2 font-medium">
|
||||
Название проекта
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
autoFocus
|
||||
type="text"
|
||||
className="py-5 border-b bg-transparent outline-none border-[#37393B] placeholder:btnl placeholder:text-[#7A7A7A] placeholder:font-medium"
|
||||
placeholder="Название"
|
||||
{...register('title', { required: true })}
|
||||
id="title"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="city" className="heading2 font-medium">
|
||||
Город
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
className="py-5 border-b bg-transparent outline-none border-[#37393B] placeholder:btnl placeholder:text-[#7A7A7A] placeholder:font-medium"
|
||||
{...register('city', { required: true })}
|
||||
id="city"
|
||||
placeholder="Екатеринбург"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<label htmlFor="tags" className="heading2 font-medium">
|
||||
Выберете категории
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(projectsTags as Product[]).map((option, index) => (
|
||||
<ProductOption
|
||||
key={index}
|
||||
text={option}
|
||||
fieldName="tags"
|
||||
setValue={setValue}
|
||||
getValues={getValues}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="city">Город</label>
|
||||
<input
|
||||
type="text"
|
||||
className="px-3 py-2 border rounded-lg outline-none border-neutral-500"
|
||||
{...register('city', { required: true })}
|
||||
id="city"
|
||||
placeholder="Город"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="releaseDate">Дата релиза</label>
|
||||
<input
|
||||
type="date"
|
||||
{...register('releaseDate', {
|
||||
valueAsDate: true,
|
||||
required: true,
|
||||
})}
|
||||
defaultValue={defaultValues?.releaseDate.split('T')[0]}
|
||||
id="releaseDate"
|
||||
className="px-3 py-2 border rounded-lg outline-none border-neutral-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="description">Описание</label>
|
||||
<textarea
|
||||
className="px-3 py-2 border rounded-lg outline-none border-neutral-500"
|
||||
placeholder="Описание"
|
||||
{...register('description', { required: false })}
|
||||
id="description"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="stage">Стадия</label>
|
||||
<select
|
||||
{...register('stage', { valueAsNumber: true })}
|
||||
id="stage"
|
||||
className="px-3 py-2 border rounded-lg outline-none border-neutral-500"
|
||||
>
|
||||
{Array.from({ length: 6 }, (_, i) => i + 1).map((stage) => (
|
||||
<option key={stage} value={stage} label={stage.toString()} />
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="company">Компания</label>
|
||||
</div>
|
||||
<MediaUploader
|
||||
dest="projects"
|
||||
setValue={setValue}
|
||||
fieldName="image"
|
||||
item={{ img: defaultValues?.image ?? '', id: '' }}
|
||||
label="Загрузите или перетащите изображение для превью (рекомендованнный размер 1080/1080 px)"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="company" className="heading2 font-medium">
|
||||
Выберете застройщика из списка или добавьте нового
|
||||
</label>
|
||||
<div className="flex gap-4">
|
||||
<select
|
||||
style={{
|
||||
backgroundImage: `url("data:image/svg+xml;charset=UTF-8,<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M12.0001 17.707L19.7072 9.99992L18.293 8.58571L12.0001 14.8786L5.70718 8.58571L4.29297 9.99992L12.0001 17.707Z' fill='white'/></svg>")`,
|
||||
}}
|
||||
id="company"
|
||||
{...register('companyId')}
|
||||
className="col-start-3 px-3 py-2 border rounded-lg outline-none border-neutral-500 h-fit"
|
||||
className="px-8 py-7 rounded-2xl outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] h-fit font-medium btnl appearance-none"
|
||||
>
|
||||
<option value={undefined}>-</option>
|
||||
<option value={undefined}>Не выбрано</option>
|
||||
{companies?.map((company) => (
|
||||
<option key={company.id} value={company.id} className="flex">
|
||||
{company.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<MediaUploader
|
||||
dest="projects"
|
||||
setValue={setValue}
|
||||
fieldName="image"
|
||||
item={{ img: defaultValues?.image ?? '', id: '' }}
|
||||
label="Выберите изображение"
|
||||
/>
|
||||
<div className="flex flex-col col-start-2">
|
||||
<label htmlFor="devices" className="w-fit">
|
||||
Категории
|
||||
</label>
|
||||
{projectsTags.map((tag) => (
|
||||
<div key={tag} className="space-x-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register('tags')}
|
||||
id={tag}
|
||||
value={tag}
|
||||
key={tag}
|
||||
/>
|
||||
<label htmlFor={tag} className="select-none">
|
||||
{tag}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<AddItemWrapper
|
||||
modal={<CompanyFormModal action={'create'} />}
|
||||
modalName="companyForm"
|
||||
>
|
||||
<Button
|
||||
rounded="2xl"
|
||||
color="secondary"
|
||||
className="bg-[#37393B99] h-full border-none gap-2"
|
||||
icon={
|
||||
<ClassNameWrapper className="w-5 h-5">
|
||||
<PlusIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</AddItemWrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Button
|
||||
className="text-white bg-black/40"
|
||||
color="secondary"
|
||||
onClick={() => setModal(null, '')}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="description" className="heading2 font-medium">
|
||||
О проекте
|
||||
</label>
|
||||
<textarea
|
||||
className="py-5 border-b bg-transparent outline-none border-[#37393B] placeholder:btnl placeholder:text-[#7A7A7A] placeholder:font-medium h-auto max-h-[300px] resize-none"
|
||||
placeholder="Описание"
|
||||
{...descriptionRegister}
|
||||
rows={1}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
id="description"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="stage" className="heading2 font-medium">
|
||||
Этап проекта
|
||||
</label>
|
||||
<select
|
||||
style={{
|
||||
backgroundImage: `url("data:image/svg+xml;charset=UTF-8,<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M12.0001 17.707L19.7072 9.99992L18.293 8.58571L12.0001 14.8786L5.70718 8.58571L4.29297 9.99992L12.0001 17.707Z' fill='white'/></svg>")`,
|
||||
}}
|
||||
{...register('stage', { valueAsNumber: true })}
|
||||
id="stage"
|
||||
className="px-8 py-7 rounded-lg outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] appearance-none"
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button className="text-white" type="submit">
|
||||
{action === 'create' ? 'Добавить проект' : 'Сохранить изменения'}
|
||||
</Button>
|
||||
{Array.from({ length: 6 }, (_, i) => i + 1).map((stage) => (
|
||||
<option
|
||||
className=""
|
||||
key={stage}
|
||||
value={stage}
|
||||
label={stage.toString()}
|
||||
/>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label htmlFor="releaseDate" className="heading2 font-medium">
|
||||
Дата релиза
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
{...register('releaseDate', {
|
||||
valueAsDate: true,
|
||||
required: true,
|
||||
})}
|
||||
defaultValue={defaultValues?.releaseDate.split('T')[0]}
|
||||
id="releaseDate"
|
||||
className="py-5 border-b border-[#37393B] rounded-lg outline-none bg-transparent text-[#7A7A7A] placeholder:text-[#7A7A7A]"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { AddItemWrapper } from '@/hocs/AddItemButton';
|
||||
import { AddItemWrapper } from '@/hocs/AddItemWrapper';
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { Title } from '@/ui/Title';
|
||||
|
||||
|
||||
@@ -149,15 +149,17 @@ export function Calculator() {
|
||||
/>
|
||||
<GradientButton
|
||||
onClick={() => setCalculated(!calculated)}
|
||||
active={calculated}
|
||||
className="flex gap-x-3 items-center max-md:absolute top-0 max-md:-mt-7 left-[calc(50%-24px)]"
|
||||
>
|
||||
<ClassNameWrapper
|
||||
element={<LogoIcon />}
|
||||
className={
|
||||
'lg:w-7 lg:h-7 w-5 h-5 ' +
|
||||
(!calculated ? 'opacity-50' : 'opacity-100')
|
||||
}
|
||||
/>
|
||||
>
|
||||
<LogoIcon />
|
||||
</ClassNameWrapper>
|
||||
</GradientButton>
|
||||
</div>
|
||||
<div className="space-y-10 lg:max-w-[1040px] w-full max-lg:order-1">
|
||||
|
||||
@@ -72,10 +72,9 @@ export function ConsultationRange({
|
||||
onMouseLeave={handleMouseUp}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<ClassNameWrapper
|
||||
className="text-[#7A7A7A] select-none w-6 h-6"
|
||||
element={<PanDotsIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="text-[#7A7A7A] select-none w-6 h-6">
|
||||
<PanDotsIcon />
|
||||
</ClassNameWrapper>
|
||||
</div>
|
||||
</div>
|
||||
<p className="self-center right-8 font-medium text-[#7A7A7A] z-[1] btnl">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { PlusIcon } from '@/components/icons/PlusIcon';
|
||||
import { ItemActions } from '@/components/ItemActions';
|
||||
import { CompanyFormModal } from '@/components/modals/CompanyFormModal';
|
||||
import { AddItemWrapper } from '@/hocs/AddItemButton';
|
||||
import { AddItemWrapper } from '@/hocs/AddItemWrapper';
|
||||
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
||||
import { useGetCompaniesQuery } from '@/queries/getCompanies';
|
||||
import { GradientButton } from '@/ui/GradientButton';
|
||||
@@ -53,7 +53,9 @@ export function Clients() {
|
||||
className="aspect-square flex flex-col items-center justify-center gap-3"
|
||||
>
|
||||
<GradientButton>
|
||||
<ClassNameWrapper element={<PlusIcon />} className="w-7 h-7" />
|
||||
<ClassNameWrapper className="w-7 h-7">
|
||||
<PlusIcon />
|
||||
</ClassNameWrapper>
|
||||
</GradientButton>
|
||||
<p className="btnl font-medium">Добавить</p>
|
||||
</AddItemWrapper>
|
||||
|
||||
@@ -71,14 +71,12 @@ export function InteractivePresentation() {
|
||||
<div className="rounded-lg bg-[#B1EC52] text-black font-medium w-12 h-12 rotate-[-4deg] content-center text-center z-[2] relative">
|
||||
2K
|
||||
</div>
|
||||
<ClassNameWrapper
|
||||
element={<Flat1 />}
|
||||
className="relative right-1 z-[1]"
|
||||
/>
|
||||
<ClassNameWrapper
|
||||
element={<Flat2 />}
|
||||
className="rotate-[-4deg] right-2 relative"
|
||||
/>
|
||||
<ClassNameWrapper className="relative right-1 z-[1]">
|
||||
<Flat1 />
|
||||
</ClassNameWrapper>
|
||||
<ClassNameWrapper className="rotate-[-4deg] right-2 relative">
|
||||
<Flat2 />
|
||||
</ClassNameWrapper>
|
||||
</div>
|
||||
<p className="font-medium text1">
|
||||
Клиент всегда видит актуальные данные об интересующем его лоте,
|
||||
@@ -96,10 +94,9 @@ export function InteractivePresentation() {
|
||||
sizes=""
|
||||
/>
|
||||
<div className="w-12 h-12 bg-[#37393B] rounded-lg flex justify-center items-center z-[1] relative right-1">
|
||||
<ClassNameWrapper
|
||||
className="rotate-[4deg]"
|
||||
element={<MailIcon />}
|
||||
/>
|
||||
<ClassNameWrapper className="rotate-[4deg]">
|
||||
<MailIcon />
|
||||
</ClassNameWrapper>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-[#37393B] rounded-lg flex justify-center items-center rotate-[-4deg] relative right-2">
|
||||
<PhoneIcon />
|
||||
|
||||
@@ -105,7 +105,9 @@ export function ProjectsSlider({
|
||||
className="px-3 py-2 rounded-xl bg-[#37393B99]"
|
||||
onClick={() => setCurrent((prev) => Math.max(prev - 1, 1))}
|
||||
>
|
||||
<ClassNameWrapper element={<ArrowLeftIcon />} className="w-4 h-4" />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<ArrowLeftIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
<button
|
||||
className="px-3 py-2 rounded-xl bg-[#37393B99]"
|
||||
@@ -113,10 +115,9 @@ export function ProjectsSlider({
|
||||
setCurrent((prev) => Math.min(projects.length, prev + 1))
|
||||
}
|
||||
>
|
||||
<ClassNameWrapper
|
||||
element={<ArrowRightIcon />}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<ArrowRightIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { PlusIcon } from '@/components/icons/PlusIcon';
|
||||
import { ProjectFormModal } from '@/components/modals/ProjectFormModal';
|
||||
import { AddItemWrapper } from '@/hocs/AddItemButton';
|
||||
import { AddItemWrapper } from '@/hocs/AddItemWrapper';
|
||||
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useGetProjectsCountQuery } from '@/queries/getProjectsCount';
|
||||
@@ -24,7 +24,7 @@ export function ProjectsPageHeader() {
|
||||
</Title>
|
||||
{auth && (
|
||||
<AddItemWrapper
|
||||
modalName="addProject"
|
||||
modalName="addProjectForm"
|
||||
modal={<ProjectFormModal action={'create'} />}
|
||||
className="btns sticky top-0"
|
||||
>
|
||||
@@ -32,7 +32,9 @@ export function ProjectsPageHeader() {
|
||||
color="primary"
|
||||
className="btns rounded-xl gap-2 py-2"
|
||||
icon={
|
||||
<ClassNameWrapper className="w-4 h-4" element={<PlusIcon />} />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<PlusIcon />
|
||||
</ClassNameWrapper>
|
||||
}
|
||||
>
|
||||
Добавить проект
|
||||
|
||||
@@ -31,7 +31,9 @@ export function ProjectsSection({ projects }: { projects: IProject[] }) {
|
||||
className="rounded-xl bg-[#232425] aspect-square h-fit self-center p-5 opacity-60 hover:opacity-100 transition-opacity flex items-center justify-center btnl font-medium gap-2"
|
||||
>
|
||||
Смотреть все
|
||||
<ClassNameWrapper className="w-5 h-5" element={<ArrowRightIcon />} />
|
||||
<ClassNameWrapper className="w-5 h-5">
|
||||
<ArrowRightIcon />
|
||||
</ClassNameWrapper>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,9 @@ export function TagsFilters({
|
||||
className="bg-gradient rounded-2xl btnm flex items-center gap-2 pl-6 py-4 pr-4 font-medium z-[11] -mb-5 mt-5"
|
||||
>
|
||||
Применить
|
||||
<ClassNameWrapper className="w-4 h-4" element={<CheckIcon />} />
|
||||
<ClassNameWrapper className="w-4 h-4">
|
||||
<CheckIcon />
|
||||
</ClassNameWrapper>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { PropsWithChildren, ReactNode, useEffect, useRef } from 'react';
|
||||
|
||||
export function AddItemWrapper({
|
||||
modal,
|
||||
modalName,
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren<{
|
||||
modal: ReactNode;
|
||||
modalName: string;
|
||||
className?: string;
|
||||
}>) {
|
||||
const { data: auth } = useCheckAuthQuery();
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth) {
|
||||
ref.current?.children.item(0)!.addEventListener('click', () => {
|
||||
console.log('asd');
|
||||
setModal(modal, modalName);
|
||||
});
|
||||
|
||||
return () => {
|
||||
ref.current?.children.item(0)!.removeEventListener('click', () => {
|
||||
setModal(modal, modalName);
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [auth]);
|
||||
|
||||
return (
|
||||
auth && (
|
||||
<div ref={ref} className={className}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
export function AddItemWrapper({
|
||||
modal,
|
||||
modalName,
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren<{
|
||||
modal: ReactNode;
|
||||
modalName: string;
|
||||
className?: string;
|
||||
}>) {
|
||||
const { data: auth } = useCheckAuthQuery();
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
return (
|
||||
auth && (
|
||||
<div
|
||||
ref={(e) => {
|
||||
e?.children
|
||||
.item(0)!
|
||||
.addEventListener('click', () => setModal(modal, modalName));
|
||||
|
||||
return () =>
|
||||
e?.children
|
||||
.item(0)
|
||||
?.removeEventListener('click', () => setModal(modal, modalName));
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
import { PropsWithChildren, useEffect, useRef } from 'react';
|
||||
|
||||
interface Props {
|
||||
element: ReactNode;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export function ClassNameWrapper({ element, className }: Props) {
|
||||
export function ClassNameWrapper({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!className.split(' ').length) return;
|
||||
|
||||
ref.current?.children.item(0)?.setAttributeNS(null, 'class', '');
|
||||
ref.current?.children
|
||||
.item(0)
|
||||
?.classList.add(...className.split(' ').filter(Boolean));
|
||||
}, [className, element]);
|
||||
}, [className, children]);
|
||||
|
||||
return <div ref={ref}>{element}</div>;
|
||||
return <div ref={ref}>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ICompany } from './ICompany';
|
||||
import { Product } from './Product';
|
||||
|
||||
export type Device = 'Stream' | 'Touch' | 'Mobile' | 'VR';
|
||||
|
||||
@@ -12,5 +13,5 @@ export interface IProject {
|
||||
image: string;
|
||||
stage: number;
|
||||
releaseDate: string;
|
||||
tags: string[];
|
||||
tags: Product[];
|
||||
}
|
||||
|
||||
@@ -2,18 +2,27 @@ import { PropsWithChildren } from 'react';
|
||||
|
||||
export function GradientButton({
|
||||
children,
|
||||
active,
|
||||
onClick,
|
||||
className,
|
||||
}: PropsWithChildren<{ onClick?: () => void; className?: string }>) {
|
||||
}: PropsWithChildren<{
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
active?: boolean;
|
||||
}>) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`bg-gradient-to-bl p-px rounded-full from-[#BE69F5] to-[#798FFF00]${
|
||||
className={`bg-gradient-to-bl outline-none p-px rounded-full from-[#BE69F5] to-[#798FFF00]${
|
||||
className ? ' ' + className : ''
|
||||
}`}
|
||||
>
|
||||
<div className="p-2 bg-black rounded-full">
|
||||
<div className="p-[14px] rounded-full bg-[#37393B99] active:bg-gradient-to-r from-[#6078F2] to-[#C868F5]">
|
||||
<div
|
||||
className={`p-[14px] rounded-full bg-[#37393B99] active:bg-gradient-to-r from-[#6078F2] to-[#C868F5]${
|
||||
active ? ' bg-gradient-to-r' : ''
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+6
-6
@@ -20,15 +20,15 @@ export function NavLink({
|
||||
<Link
|
||||
href={href}
|
||||
className={
|
||||
`relative btn-text font-medium text-[#9299BD] [&:not(:last-child)]:border-r px-10 gap-x-2 border-[#3D425C] hover:text-white ${pathname === href ? 'text-white ' : ''}py-6 card-gradient-2 active:bg-[#14161F]` +
|
||||
className
|
||||
`relative btn-text font-medium text-[#9299BD] [&:not(:last-child)]:border-r px-10 gap-x-2 border-[#3D425C] hover:text-white ${
|
||||
pathname === href ? 'text-white ' : ''
|
||||
}py-6 card-gradient-2 active:bg-[#14161F]` + className
|
||||
}
|
||||
>
|
||||
{pathname === href && (
|
||||
<ClassNameWrapper
|
||||
element={<CubeIcon />}
|
||||
className="text-[#798FFF] absolute left-5"
|
||||
/>
|
||||
<ClassNameWrapper className="text-[#798FFF] absolute left-5">
|
||||
<CubeIcon />
|
||||
</ClassNameWrapper>
|
||||
)}
|
||||
{children}
|
||||
</Link>
|
||||
|
||||
@@ -642,6 +642,11 @@ ast-types-flow@^0.0.8:
|
||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
|
||||
integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
|
||||
|
||||
attr-accept@^2.2.4:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.5.tgz#d7061d958e6d4f97bf8665c68b75851a0713ab5e"
|
||||
integrity sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==
|
||||
|
||||
autoprefixer@^10.4.19:
|
||||
version "10.4.19"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f"
|
||||
@@ -1447,6 +1452,13 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-selector@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-2.1.2.tgz#fe7c7ee9e550952dfbc863d73b14dc740d7de8b4"
|
||||
integrity sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==
|
||||
dependencies:
|
||||
tslib "^2.7.0"
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
@@ -2561,6 +2573,15 @@ react-dom@^18:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.2"
|
||||
|
||||
react-dropzone@^14.3.5:
|
||||
version "14.3.5"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.3.5.tgz#1a8bd312c8a353ec78ef402842ccb3589c225add"
|
||||
integrity sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==
|
||||
dependencies:
|
||||
attr-accept "^2.2.4"
|
||||
file-selector "^2.1.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-hook-form@^7.53.0:
|
||||
version "7.53.0"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab"
|
||||
@@ -3114,6 +3135,11 @@ tslib@^2.4.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
|
||||
tslib@^2.7.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
|
||||
Reference in New Issue
Block a user