upd
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 14.3889C5.31872 14.3889 4.36111 15.3465 4.36111 16.5278C4.36111 17.7091 5.31872 18.6667 6.5 18.6667C7.68128 18.6667 8.63889 17.7091 8.63889 16.5278C8.63889 15.3465 7.68128 14.3889 6.5 14.3889ZM2.63407 15.6111C3.04789 13.8592 4.6217 12.5556 6.5 12.5556C8.3783 12.5556 9.95211 13.8592 10.3659 15.6111H23V17.4444H10.3659C9.95211 19.1963 8.3783 20.5 6.5 20.5C4.6217 20.5 3.04789 19.1963 2.63407 17.4444H1V15.6111H2.63407Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 5.83333C18.6813 5.83333 19.6389 6.79095 19.6389 7.97222C19.6389 9.1535 18.6813 10.1111 17.5 10.1111C16.3187 10.1111 15.3611 9.1535 15.3611 7.97222C15.3611 6.79095 16.3187 5.83333 17.5 5.83333ZM21.3659 7.05556C20.9521 5.30368 19.3783 4 17.5 4C15.6217 4 14.0479 5.30368 13.6341 7.05556H1V8.88889H13.6341C14.0479 10.6408 15.6217 11.9444 17.5 11.9444C19.3783 11.9444 20.9521 10.6408 21.3659 8.88889H23V7.05556H21.3659Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,18 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { ArticleActions } from '@/components/ArticleActions';
|
||||
import { ArticleHeader } from '@/components/ArticleHeader';
|
||||
import { ArticleContentInput } from '@/components/articleInputs/ArticleContentInput';
|
||||
import { ArticleSliderInput } from '@/components/articleInputs/ArticleSliderInput';
|
||||
import { IArticleFormInput } from '@/components/modals/ArticleFormModal';
|
||||
import { ArticleContent } from '@/components/pages/BlogPage/ArticleContent';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { reorderFields } from '@/utils/reorderFields';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFieldArray, useForm, useWatch } from 'react-hook-form';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function DashboardArticlePage() {
|
||||
const { articleId } = useParams<{ articleId: string }>();
|
||||
@@ -20,13 +13,13 @@ export default function DashboardArticlePage() {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [article, setArticle] = useState<IArticle>();
|
||||
|
||||
const { control, handleSubmit, setValue, getValues, watch } =
|
||||
useForm<IArticleFormInput>();
|
||||
// const { control, handleSubmit, setValue, getValues, watch } =
|
||||
// useForm<IArticleFormInput>();
|
||||
|
||||
const { fields, append, swap, remove, insert } = useFieldArray({
|
||||
name: 'blocks',
|
||||
control,
|
||||
});
|
||||
// const { fields, append, swap, remove, insert } = useFieldArray({
|
||||
// name: 'blocks',
|
||||
// control,
|
||||
// });
|
||||
|
||||
// useGetArticleByIdQuery({
|
||||
// variables: { id: +articleId },
|
||||
@@ -55,22 +48,22 @@ export default function DashboardArticlePage() {
|
||||
|
||||
// const client = useApolloClient();
|
||||
|
||||
const [title, description, createdAt, cardImage, tags, blocks] = useWatch({
|
||||
name: ['title', 'description', 'createdAt', 'cardImage', 'tags', 'blocks'],
|
||||
control,
|
||||
});
|
||||
// const [title, description, createdAt, cardImage, tags, blocks] = useWatch({
|
||||
// name: ['title', 'description', 'createdAt', 'cardImage', 'tags', 'blocks'],
|
||||
// control,
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
setArticle({
|
||||
title,
|
||||
description,
|
||||
createdAt: new Date(createdAt),
|
||||
cardImage,
|
||||
tags,
|
||||
blocks,
|
||||
id: articleId,
|
||||
});
|
||||
}, [title, description, createdAt, cardImage, tags, blocks, articleId]);
|
||||
// useEffect(() => {
|
||||
// setArticle({
|
||||
// title,
|
||||
// description,
|
||||
// createdAt: new Date(createdAt),
|
||||
// cardImage,
|
||||
// tags,
|
||||
// blocks,
|
||||
// id: articleId,
|
||||
// });
|
||||
// }, [title, description, createdAt, cardImage, tags, blocks, articleId]);
|
||||
|
||||
async function onSubmit() {
|
||||
if (!article) return;
|
||||
@@ -81,7 +74,7 @@ export default function DashboardArticlePage() {
|
||||
// blocks: JSON.stringify(getValues('blocks')),
|
||||
// });
|
||||
// client.refetchQueries({ include: ['GetArticlesCount'] });
|
||||
window.location.href = '/blog';
|
||||
// window.location.href = '/blog';
|
||||
}
|
||||
|
||||
if (!article) return <div>not found</div>;
|
||||
@@ -98,9 +91,9 @@ export default function DashboardArticlePage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<ArticleActions {...{ ...article, setValue }} />
|
||||
{/* <ArticleActions {...{ ...article, setValue }} /> */}
|
||||
{article.title && <ArticleHeader {...article} />}
|
||||
<form className="space-y-5" onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* <form className="space-y-5" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex gap-x-4 absolute top-4">
|
||||
<Button type="submit">Сохранить</Button>
|
||||
<Button onClick={() => setShowPreview(true)}>
|
||||
@@ -132,7 +125,7 @@ export default function DashboardArticlePage() {
|
||||
onReorder={reorderFields(fields, swap)}
|
||||
className="grid grid-cols-4 gap-y-20"
|
||||
>
|
||||
{fields.map((item, index) =>
|
||||
{/* {fields.map((item, index) =>
|
||||
item.type === 'Slider' ? (
|
||||
<ArticleSliderInput
|
||||
{...{ control, index, item, setValue, remove }}
|
||||
@@ -154,7 +147,7 @@ export default function DashboardArticlePage() {
|
||||
)
|
||||
)}
|
||||
</Reorder.Group>
|
||||
</form>
|
||||
</form> */}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
+2
-17
@@ -3,8 +3,10 @@ import { Awards } from '@/components/pages/MainPage/Awards';
|
||||
import { Calculator } from '@/components/pages/MainPage/Calculator/Calculator';
|
||||
import { Clients } from '@/components/pages/MainPage/Clients';
|
||||
import { ProjectsMap } from '@/components/pages/MainPage/Map/ProjectsMap';
|
||||
import { Motivation } from '@/components/pages/MainPage/Motivation';
|
||||
import { Presentation } from '@/components/pages/MainPage/Presentation/Presentation';
|
||||
import { Projects } from '@/components/pages/MainPage/Projects';
|
||||
import { Reviews } from '@/components/pages/MainPage/Reviews/Reviews';
|
||||
import { Statistics } from '@/components/pages/MainPage/Statistics';
|
||||
import { Streaming } from '@/components/pages/MainPage/Streaming/Streaming';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
@@ -14,23 +16,6 @@ import {
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Motivation = dynamic(
|
||||
() =>
|
||||
import('@/components/pages/MainPage/Motivation').then(
|
||||
(mod) => mod.Motivation
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const Reviews = dynamic(
|
||||
() =>
|
||||
import('@/components/pages/MainPage/Reviews/Reviews').then(
|
||||
(mod) => mod.Reviews
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default async function HomePage() {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
+7
-1
@@ -1,6 +1,7 @@
|
||||
import { ModalContainer } from '@/components/Layout/ModalContainer';
|
||||
import { Providers } from '@/lib/Providers';
|
||||
// import { Providers } from '@/lib/Providers';
|
||||
import type { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Script from 'next/script';
|
||||
import './globals.css';
|
||||
|
||||
@@ -26,6 +27,11 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
const Providers = dynamic(
|
||||
() => import('@/lib/Providers').then((mod) => mod.Providers),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
|
||||
@@ -1,90 +1,90 @@
|
||||
'use client';
|
||||
// 'use client';
|
||||
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { UseFormSetValue } from 'react-hook-form';
|
||||
import { ArticleFormModal, IArticleInput } from './modals/ArticleFormModal';
|
||||
import { DeleteArticleModal } from './modals/DeleteArticleModal';
|
||||
// import { useModalStore } from '@/stores/useModalStore';
|
||||
// import { IArticle } from '@/types/IArticle';
|
||||
// import { Button } from '@/ui/Button';
|
||||
// import { Icon } from '@/ui/Icon';
|
||||
// import { usePathname, useRouter } from 'next/navigation';
|
||||
// import { UseFormSetValue } from 'react-hook-form';
|
||||
// import { ArticleFormModal, IArticleInput } from './modals/ArticleFormModal';
|
||||
// import { DeleteArticleModal } from './modals/DeleteArticleModal';
|
||||
|
||||
export function ArticleActions({
|
||||
blocks,
|
||||
cardImage,
|
||||
createdAt,
|
||||
tags,
|
||||
title,
|
||||
id,
|
||||
setValue,
|
||||
inCard = false,
|
||||
}: IArticle & {
|
||||
setValue?: UseFormSetValue<IArticleInput>;
|
||||
inCard?: boolean;
|
||||
}) {
|
||||
const { push } = useRouter();
|
||||
// export function ArticleActions({
|
||||
// blocks,
|
||||
// cardImage,
|
||||
// createdAt,
|
||||
// tags,
|
||||
// title,
|
||||
// id,
|
||||
// setValue,
|
||||
// inCard = false,
|
||||
// }: IArticle & {
|
||||
// setValue?: UseFormSetValue<IArticleInput>;
|
||||
// inCard?: boolean;
|
||||
// }) {
|
||||
// const { push } = useRouter();
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
// const { setModal } = useModalStore();
|
||||
|
||||
function handleEditArticle() {
|
||||
if (setValue)
|
||||
setModal(
|
||||
<ArticleFormModal
|
||||
set={setValue}
|
||||
defaultValues={{
|
||||
cardImage,
|
||||
createdAt: createdAt.toISOString().split('T')[0],
|
||||
tags,
|
||||
title,
|
||||
blocks,
|
||||
}}
|
||||
/>,
|
||||
'editArticle'
|
||||
);
|
||||
else push(`/blog/${id}/edit`);
|
||||
}
|
||||
// function handleEditArticle() {
|
||||
// if (setValue)
|
||||
// setModal(
|
||||
// <ArticleFormModal
|
||||
// set={setValue}
|
||||
// defaultValues={{
|
||||
// cardImage,
|
||||
// createdAt: createdAt.toISOString().split('T')[0],
|
||||
// tags,
|
||||
// title,
|
||||
// blocks,
|
||||
// }}
|
||||
// />,
|
||||
// 'editArticle'
|
||||
// );
|
||||
// else push(`/blog/${id}/edit`);
|
||||
// }
|
||||
|
||||
function handleDeleteArticle() {
|
||||
setModal(<DeleteArticleModal id={id} />, 'deleteArticle');
|
||||
}
|
||||
// function handleDeleteArticle() {
|
||||
// setModal(<DeleteArticleModal id={id} />, 'deleteArticle');
|
||||
// }
|
||||
|
||||
const pathname = usePathname();
|
||||
// const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-4 right-0 p-4 gap-2 z-[5] ${
|
||||
inCard ? 'group-hover:flex hidden' : 'flex'
|
||||
}`}
|
||||
>
|
||||
{inCard ? (
|
||||
<>
|
||||
<button
|
||||
onClick={handleEditArticle}
|
||||
className="bg-opacity-60 hover:bg-opacity-70 relative p-2 transition-opacity bg-black rounded-full"
|
||||
>
|
||||
<Icon name="edit" color="white" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDeleteArticle}
|
||||
className="bg-opacity-60 hover:bg-opacity-70 relative p-2 transition-opacity bg-black rounded-full"
|
||||
>
|
||||
<Icon name="trash" color="white" />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button onClick={handleEditArticle}>
|
||||
Редактировать {pathname.endsWith('edit') ? 'карточку' : 'статью'}
|
||||
</Button>
|
||||
<Button
|
||||
color="secondary"
|
||||
className="bg-[red]"
|
||||
onClick={handleDeleteArticle}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div
|
||||
// className={`absolute top-4 right-0 p-4 gap-2 z-[5] ${
|
||||
// inCard ? 'group-hover:flex hidden' : 'flex'
|
||||
// }`}
|
||||
// >
|
||||
// {inCard ? (
|
||||
// <>
|
||||
// <button
|
||||
// onClick={handleEditArticle}
|
||||
// className="bg-opacity-60 hover:bg-opacity-70 relative p-2 transition-opacity bg-black rounded-full"
|
||||
// >
|
||||
// <Icon name="edit" color="white" />
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={handleDeleteArticle}
|
||||
// className="bg-opacity-60 hover:bg-opacity-70 relative p-2 transition-opacity bg-black rounded-full"
|
||||
// >
|
||||
// <Icon name="trash" color="white" />
|
||||
// </button>
|
||||
// </>
|
||||
// ) : (
|
||||
// <>
|
||||
// <Button onClick={handleEditArticle}>
|
||||
// Редактировать {pathname.endsWith('edit') ? 'карточку' : 'статью'}
|
||||
// </Button>
|
||||
// <Button
|
||||
// color="secondary"
|
||||
// className="bg-[red]"
|
||||
// onClick={handleDeleteArticle}
|
||||
// >
|
||||
// Удалить
|
||||
// </Button>
|
||||
// </>
|
||||
// )}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IContent } from '@/types/IArticle';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { RefObject, SyntheticEvent } from 'react';
|
||||
import {
|
||||
Control,
|
||||
UseFieldArrayInsert,
|
||||
UseFieldArrayRemove,
|
||||
} from 'react-hook-form';
|
||||
import { ArticleContentEditorModal } from './modals/ArticleContentEditorModal';
|
||||
import { IArticleFormInput } from './modals/ArticleFormModal';
|
||||
// import { useModalStore } from '@/stores/useModalStore';
|
||||
// import { IContent } from '@/types/IArticle';
|
||||
// import { Icon } from '@/ui/Icon';
|
||||
// import { RefObject, SyntheticEvent } from 'react';
|
||||
// import {
|
||||
// Control,
|
||||
// UseFieldArrayInsert,
|
||||
// UseFieldArrayRemove,
|
||||
// } from 'react-hook-form';
|
||||
// import { ArticleContentEditorModal } from './modals/ArticleContentEditorModal';
|
||||
// import { IArticleFormInput } from './modals/ArticleFormModal';
|
||||
|
||||
interface IBlockActionsProps {
|
||||
item: IContent & Record<'id', string>;
|
||||
index: number;
|
||||
control: Control<IArticleFormInput, any>;
|
||||
remove: UseFieldArrayRemove;
|
||||
insert: UseFieldArrayInsert<IArticleFormInput, 'blocks'>;
|
||||
btnref: RefObject<HTMLButtonElement>;
|
||||
}
|
||||
// interface IBlockActionsProps {
|
||||
// item: IContent & Record<'id', string>;
|
||||
// index: number;
|
||||
// control: Control<IArticleFormInput, any>;
|
||||
// remove: UseFieldArrayRemove;
|
||||
// insert: UseFieldArrayInsert<IArticleFormInput, 'blocks'>;
|
||||
// btnref: RefObject<HTMLButtonElement>;
|
||||
// }
|
||||
|
||||
export function BlockActions({
|
||||
control,
|
||||
index,
|
||||
item,
|
||||
remove,
|
||||
insert,
|
||||
btnref,
|
||||
}: IBlockActionsProps) {
|
||||
const { setModal } = useModalStore();
|
||||
// export function BlockActions({
|
||||
// control,
|
||||
// index,
|
||||
// item,
|
||||
// remove,
|
||||
// insert,
|
||||
// btnref,
|
||||
// }: IBlockActionsProps) {
|
||||
// const { setModal } = useModalStore();
|
||||
|
||||
function handleEditBlock(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
setModal(
|
||||
<ArticleContentEditorModal {...{ control, index, item }} />,
|
||||
'content'
|
||||
);
|
||||
}
|
||||
// function handleEditBlock(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// setModal(
|
||||
// <ArticleContentEditorModal {...{ control, index, item }} />,
|
||||
// 'content'
|
||||
// );
|
||||
// }
|
||||
|
||||
function handleDublicateBlock(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
insert(index, item);
|
||||
}
|
||||
// function handleDublicateBlock(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// insert(index, item);
|
||||
// }
|
||||
|
||||
function handleRemoveBlock(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
remove(index);
|
||||
}
|
||||
// function handleRemoveBlock(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// remove(index);
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="flex gap-x-4 absolute lg:-right-[150px] max-lg:top-full">
|
||||
<button ref={btnref} onClick={handleEditBlock}>
|
||||
<Icon name="edit" color="white" />
|
||||
</button>
|
||||
<button
|
||||
className="border p-2 rounded-full"
|
||||
onClick={handleDublicateBlock}
|
||||
>
|
||||
<Icon name="add" color="white" />
|
||||
</button>
|
||||
<button onClick={handleRemoveBlock}>
|
||||
<Icon name="trash" color="white" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div className="flex gap-x-4 absolute lg:-right-[150px] max-lg:top-full">
|
||||
// <button ref={btnref} onClick={handleEditBlock}>
|
||||
// <Icon name="edit" color="white" />
|
||||
// </button>
|
||||
// <button
|
||||
// className="border p-2 rounded-full"
|
||||
// onClick={handleDublicateBlock}
|
||||
// >
|
||||
// <Icon name="add" color="white" />
|
||||
// </button>
|
||||
// <button onClick={handleRemoveBlock}>
|
||||
// <Icon name="trash" color="white" />
|
||||
// </button>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -16,6 +16,8 @@ import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import BurgerIcon from '../../../public/icons/burger.svg';
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
import { LogoIcon } from '../icons/LogoIcon';
|
||||
import { LogoWithTextIcon } from '../icons/LogoWithTextIcon';
|
||||
import { ModalWithFeedbackForm } from '../modals/ModalWithFeedbackForm';
|
||||
@@ -131,11 +133,11 @@ export function Header() {
|
||||
className="!border-none p-[18px] hover:bg-[#232425] rounded-2xl active:opacity-50 outline-none"
|
||||
onClick={() => setBurgerOpened((prev) => !prev)}
|
||||
>
|
||||
<Icon
|
||||
name={burgerOpened ? 'close' : 'burger'}
|
||||
color="white"
|
||||
size={16}
|
||||
/>
|
||||
{burgerOpened ? (
|
||||
<CloseIcon className="w-4 h-4" />
|
||||
) : (
|
||||
<BurgerIcon className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{auth && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import countries from 'countries-phone-masks';
|
||||
import {
|
||||
CountryCode,
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
import Image from 'next/image';
|
||||
import { SyntheticEvent, useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import ChevronDownIcon from '../../../public/icons/chevron_down.svg';
|
||||
import ChevronUpIcon from '../../../public/icons/chevron_up.svg';
|
||||
|
||||
export function SelectPhoneCode({
|
||||
currentPhoneCodeAndCountry: [currentPhoneCode, currentCountry],
|
||||
@@ -47,11 +48,11 @@ export function SelectPhoneCode({
|
||||
sizes=""
|
||||
/>
|
||||
<p className="btnl font-medium">{currentPhoneCode}</p>
|
||||
<Icon
|
||||
name={open ? 'chevron_up' : 'chevron_down'}
|
||||
color="white"
|
||||
svgProp={{ className: 'max-sm:w-4 sm:max-lg:w-5 flex-1' }}
|
||||
/>
|
||||
{open ? (
|
||||
<ChevronUpIcon className="max-sm:w-4 sm:max-lg:w-5 flex-1 text-white" />
|
||||
) : (
|
||||
<ChevronDownIcon className="max-sm:w-4 sm:max-lg:w-5 flex-1 text-white" />
|
||||
)}
|
||||
</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,61 +1,61 @@
|
||||
import { IContent } from '@/types/IArticle';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import parse from 'html-react-parser';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Control,
|
||||
UseFieldArrayInsert,
|
||||
UseFieldArrayRemove,
|
||||
UseFormSetValue,
|
||||
UseFormWatch,
|
||||
} from 'react-hook-form';
|
||||
import { BlockActions } from '../BlockActions';
|
||||
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
// import { IContent } from '@/types/IArticle';
|
||||
// import { Reorder } from 'framer-motion';
|
||||
// import parse from 'html-react-parser';
|
||||
// import { useEffect, useRef, useState } from 'react';
|
||||
// import {
|
||||
// Control,
|
||||
// UseFieldArrayInsert,
|
||||
// UseFieldArrayRemove,
|
||||
// UseFormSetValue,
|
||||
// UseFormWatch,
|
||||
// } from 'react-hook-form';
|
||||
// import { BlockActions } from '../BlockActions';
|
||||
// import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
|
||||
export interface IArticleContentInputProps {
|
||||
item: IContent & Record<'id', string>;
|
||||
index: number;
|
||||
control: Control<IArticleFormInput, any>;
|
||||
setValue: UseFormSetValue<IArticleFormInput>;
|
||||
watch: UseFormWatch<IArticleFormInput>;
|
||||
remove: UseFieldArrayRemove;
|
||||
insert: UseFieldArrayInsert<IArticleFormInput, 'blocks'>;
|
||||
}
|
||||
// export interface IArticleContentInputProps {
|
||||
// item: IContent & Record<'id', string>;
|
||||
// index: number;
|
||||
// control: Control<IArticleFormInput, any>;
|
||||
// setValue: UseFormSetValue<IArticleFormInput>;
|
||||
// watch: UseFormWatch<IArticleFormInput>;
|
||||
// remove: UseFieldArrayRemove;
|
||||
// insert: UseFieldArrayInsert<IArticleFormInput, 'blocks'>;
|
||||
// }
|
||||
|
||||
export function ArticleContentInput({
|
||||
control,
|
||||
index,
|
||||
item,
|
||||
setValue,
|
||||
watch,
|
||||
remove,
|
||||
insert,
|
||||
}: IArticleContentInputProps) {
|
||||
const [content, setContent] = useState(item.content);
|
||||
// export function ArticleContentInput({
|
||||
// control,
|
||||
// index,
|
||||
// item,
|
||||
// setValue,
|
||||
// watch,
|
||||
// remove,
|
||||
// insert,
|
||||
// }: IArticleContentInputProps) {
|
||||
// const [content, setContent] = useState(item.content);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = watch((value) => {
|
||||
if (value.blocks?.[index]?.type === 'Content')
|
||||
setContent(value.blocks[index].content!);
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [index, setValue, watch]);
|
||||
// useEffect(() => {
|
||||
// const { unsubscribe } = watch((value) => {
|
||||
// if (value.blocks?.[index]?.type === 'Content')
|
||||
// setContent(value.blocks[index].content!);
|
||||
// });
|
||||
// return unsubscribe;
|
||||
// }, [index, setValue, watch]);
|
||||
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
// const ref = useRef<HTMLButtonElement>(null);
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
as="div"
|
||||
value={item}
|
||||
className="border-[#3D425C] border p-4 rounded-3xl relative flex lg:col-start-2 lg:col-span-2 sm:col-span-3 col-span-full"
|
||||
onDoubleClick={() => ref.current && ref.current?.click()}
|
||||
>
|
||||
<div className="no-tailwindcss-base">{parse(content)}</div>
|
||||
<BlockActions
|
||||
btnref={ref}
|
||||
{...{ control, index, remove, insert }}
|
||||
item={{ ...item, content }}
|
||||
/>
|
||||
</Reorder.Item>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <Reorder.Item
|
||||
// as="div"
|
||||
// value={item}
|
||||
// className="border-[#3D425C] border p-4 rounded-3xl relative flex lg:col-start-2 lg:col-span-2 sm:col-span-3 col-span-full"
|
||||
// onDoubleClick={() => ref.current && ref.current?.click()}
|
||||
// >
|
||||
// <div className="no-tailwindcss-base">{parse(content)}</div>
|
||||
// <BlockActions
|
||||
// btnref={ref}
|
||||
// {...{ control, index, remove, insert }}
|
||||
// item={{ ...item, content }}
|
||||
// />
|
||||
// </Reorder.Item>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { UseFieldArrayRemove, UseFormSetValue } from 'react-hook-form';
|
||||
import { ImageUploader } from '../ImageUploader';
|
||||
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
// import { Icon } from '@/ui/Icon';
|
||||
// import { Reorder } from 'framer-motion';
|
||||
// import { SyntheticEvent } from 'react';
|
||||
// import { UseFieldArrayRemove, UseFormSetValue } from 'react-hook-form';
|
||||
// import { ImageUploader } from '../ImageUploader';
|
||||
// import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
|
||||
interface IArticleSliderImageInputProps {
|
||||
setValue: UseFormSetValue<IArticleFormInput>;
|
||||
remove: UseFieldArrayRemove;
|
||||
item: Record<'img', string> & Record<'id', string>;
|
||||
index: number;
|
||||
imgIndex: number;
|
||||
}
|
||||
// interface IArticleSliderImageInputProps {
|
||||
// setValue: UseFormSetValue<IArticleFormInput>;
|
||||
// remove: UseFieldArrayRemove;
|
||||
// item: Record<'img', string> & Record<'id', string>;
|
||||
// index: number;
|
||||
// imgIndex: number;
|
||||
// }
|
||||
|
||||
export function ArticleSliderImageInput({
|
||||
item,
|
||||
imgIndex,
|
||||
index,
|
||||
remove,
|
||||
setValue,
|
||||
}: IArticleSliderImageInputProps) {
|
||||
function handleClickClose(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
remove(imgIndex);
|
||||
}
|
||||
// export function ArticleSliderImageInput({
|
||||
// item,
|
||||
// imgIndex,
|
||||
// index,
|
||||
// remove,
|
||||
// setValue,
|
||||
// }: IArticleSliderImageInputProps) {
|
||||
// function handleClickClose(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// remove(imgIndex);
|
||||
// }
|
||||
|
||||
return (
|
||||
<Reorder.Item value={item} className="flex items-center" drag as="div">
|
||||
<ImageUploader
|
||||
dest="blog"
|
||||
setValue={setValue}
|
||||
fieldName={`blocks.${index}.images.${imgIndex}.img`}
|
||||
item={item}
|
||||
label="Выберите изображение"
|
||||
/>
|
||||
<button className="self-start z-[1]" onClick={handleClickClose}>
|
||||
<Icon name="close" color="white" />
|
||||
</button>
|
||||
</Reorder.Item>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <Reorder.Item value={item} className="flex items-center" drag as="div">
|
||||
// <ImageUploader
|
||||
// dest="blog"
|
||||
// setValue={setValue}
|
||||
// fieldName={`blocks.${index}.images.${imgIndex}.img`}
|
||||
// item={item}
|
||||
// label="Выберите изображение"
|
||||
// />
|
||||
// <button className="self-start z-[1]" onClick={handleClickClose}>
|
||||
// <Icon name="close" color="white" />
|
||||
// </button>
|
||||
// </Reorder.Item>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
import { ISlider } from '@/types/IArticle';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { reorderFields } from '@/utils/reorderFields';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import {
|
||||
Control,
|
||||
useFieldArray,
|
||||
UseFieldArrayRemove,
|
||||
UseFormSetValue,
|
||||
} from 'react-hook-form';
|
||||
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
import { ArticleSliderImageInput } from './ArticleSliderImageInput';
|
||||
// import { ISlider } from '@/types/IArticle';
|
||||
// import { Icon } from '@/ui/Icon';
|
||||
// import { reorderFields } from '@/utils/reorderFields';
|
||||
// import { Reorder } from 'framer-motion';
|
||||
// import { SyntheticEvent } from 'react';
|
||||
// import {
|
||||
// Control,
|
||||
// useFieldArray,
|
||||
// UseFieldArrayRemove,
|
||||
// UseFormSetValue,
|
||||
// } from 'react-hook-form';
|
||||
// import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||
// import { ArticleSliderImageInput } from './ArticleSliderImageInput';
|
||||
|
||||
interface IArticleSliderInputProps {
|
||||
setValue: UseFormSetValue<IArticleFormInput>;
|
||||
item: ISlider & Record<'id', string>;
|
||||
index: number;
|
||||
control: Control<IArticleFormInput, any>;
|
||||
remove: UseFieldArrayRemove;
|
||||
}
|
||||
// interface IArticleSliderInputProps {
|
||||
// setValue: UseFormSetValue<IArticleFormInput>;
|
||||
// item: ISlider & Record<'id', string>;
|
||||
// index: number;
|
||||
// control: Control<IArticleFormInput, any>;
|
||||
// remove: UseFieldArrayRemove;
|
||||
// }
|
||||
|
||||
export function ArticleSliderInput({
|
||||
index,
|
||||
item,
|
||||
setValue,
|
||||
control,
|
||||
remove: removeSlider,
|
||||
}: IArticleSliderInputProps) {
|
||||
const { swap, append, remove, fields } = useFieldArray({
|
||||
control,
|
||||
name: `blocks.${index}.images`,
|
||||
});
|
||||
// export function ArticleSliderInput({
|
||||
// index,
|
||||
// item,
|
||||
// setValue,
|
||||
// control,
|
||||
// remove: removeSlider,
|
||||
// }: IArticleSliderInputProps) {
|
||||
// const { swap, append, remove, fields } = useFieldArray({
|
||||
// control,
|
||||
// name: `blocks.${index}.images`,
|
||||
// });
|
||||
|
||||
function handleAddSlide(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
append({ img: '' });
|
||||
}
|
||||
// function handleAddSlide(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// append({ img: '' });
|
||||
// }
|
||||
|
||||
function handleRemoveSlider(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
removeSlider(index);
|
||||
}
|
||||
// function handleRemoveSlider(e: SyntheticEvent) {
|
||||
// e.preventDefault();
|
||||
// removeSlider(index);
|
||||
// }
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
value={item}
|
||||
as="div"
|
||||
className="border border-[#3D425C] rounded-3xl p-4 col-span-full space-y-4"
|
||||
>
|
||||
<div className="flex gap-x-4 justify-center">
|
||||
<button onClick={handleAddSlide}>
|
||||
<Icon name="add" color="white" />
|
||||
</button>
|
||||
<button onClick={handleRemoveSlider}>
|
||||
<Icon name="trash" color="white" />
|
||||
</button>
|
||||
</div>
|
||||
<Reorder.Group
|
||||
as="div"
|
||||
axis="x"
|
||||
values={fields}
|
||||
onReorder={reorderFields(fields, swap)}
|
||||
className="flex gap-4 flex-wrap"
|
||||
>
|
||||
{fields.map((item, imgIndex) => (
|
||||
<ArticleSliderImageInput
|
||||
key={item.id}
|
||||
{...{ imgIndex, item, setValue, remove, index }}
|
||||
/>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
</Reorder.Item>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <Reorder.Item
|
||||
// value={item}
|
||||
// as="div"
|
||||
// className="border border-[#3D425C] rounded-3xl p-4 col-span-full space-y-4"
|
||||
// >
|
||||
// <div className="flex gap-x-4 justify-center">
|
||||
// <button onClick={handleAddSlide}>
|
||||
// <Icon name="add" color="white" />
|
||||
// </button>
|
||||
// <button onClick={handleRemoveSlider}>
|
||||
// <Icon name="trash" color="white" />
|
||||
// </button>
|
||||
// </div>
|
||||
// <Reorder.Group
|
||||
// as="div"
|
||||
// axis="x"
|
||||
// values={fields}
|
||||
// onReorder={reorderFields(fields, swap)}
|
||||
// className="flex gap-4 flex-wrap"
|
||||
// >
|
||||
// {fields.map((item, imgIndex) => (
|
||||
// <ArticleSliderImageInput
|
||||
// key={item.id}
|
||||
// {...{ imgIndex, item, setValue, remove, index }}
|
||||
// />
|
||||
// ))}
|
||||
// </Reorder.Group>
|
||||
// </Reorder.Item>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,132 +1,132 @@
|
||||
import { api } from '@/api';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import { useRef } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Editor as Tinymce } from 'tinymce';
|
||||
import { IArticleContentInputProps } from '../articleInputs/ArticleContentInput';
|
||||
// import { api } from '@/api';
|
||||
// import { useModalStore } from '@/stores/useModalStore';
|
||||
// import { Icon } from '@/ui/Icon';
|
||||
// import { Editor } from '@tinymce/tinymce-react';
|
||||
// import { useRef } from 'react';
|
||||
// import { Controller } from 'react-hook-form';
|
||||
// import { Editor as Tinymce } from 'tinymce';
|
||||
// import { IArticleContentInputProps } from '../articleInputs/ArticleContentInput';
|
||||
|
||||
export function ArticleContentEditorModal({
|
||||
control,
|
||||
index,
|
||||
item,
|
||||
}: Pick<IArticleContentInputProps, 'control' | 'index' | 'item'>) {
|
||||
const { setModal } = useModalStore();
|
||||
// export function ArticleContentEditorModal({
|
||||
// control,
|
||||
// index,
|
||||
// item,
|
||||
// }: Pick<IArticleContentInputProps, 'control' | 'index' | 'item'>) {
|
||||
// const { setModal } = useModalStore();
|
||||
|
||||
const editorRef = useRef<Tinymce | null>(null);
|
||||
const videoUploadRef = useRef<HTMLInputElement>(null);
|
||||
// const editorRef = useRef<Tinymce | null>(null);
|
||||
// const videoUploadRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`blocks.${index}.content`}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Editor
|
||||
id={item.id}
|
||||
onEditorChange={onChange}
|
||||
value={value}
|
||||
onInit={(_, editor) => {
|
||||
editorRef.current = editor;
|
||||
}}
|
||||
init={{
|
||||
content_style:
|
||||
'body {color: #fff; background: #14161f; font-size:16px;display:grid;grid-template-columns:repeat(4,1fr);} body > * {grid-column-start: 2;grid-column-end:4',
|
||||
height: '100%',
|
||||
font_size_formats: '10px 12px 14px 16px 18px 20px 24px 28px 30px',
|
||||
video_template_callback: (data: { source: string }) =>
|
||||
'<video src="' +
|
||||
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) => {
|
||||
const formData = new FormData();
|
||||
formData.append('files', blobInfo.blob(), blobInfo.filename());
|
||||
formData.append('dest', 'blog');
|
||||
const res = await api
|
||||
.post('upload', { body: formData })
|
||||
.json<{ files: string[] }>();
|
||||
return process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0];
|
||||
},
|
||||
file_picker_types: 'image media',
|
||||
file_picker_callback: async (cb) => {
|
||||
videoUploadRef.current!.onchange = async function () {
|
||||
const reader = new FileReader();
|
||||
const file = videoUploadRef.current?.files?.[0];
|
||||
reader.onload = async function () {
|
||||
const id = 'blobId' + new Date().getTime();
|
||||
const blobCache = editorRef.current?.editorUpload.blobCache;
|
||||
const base64 = reader.result?.toString().split(',')[1];
|
||||
const blobInfo = blobCache?.create(id, file!, base64!);
|
||||
blobCache?.add(blobInfo!);
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
'files',
|
||||
blobInfo?.blob()!,
|
||||
blobInfo?.filename()
|
||||
);
|
||||
formData.append('dest', 'blog');
|
||||
const res = await api
|
||||
.post('upload', { body: formData })
|
||||
.json<{ files: string[] }>();
|
||||
// return (
|
||||
// <div className="relative w-full h-full">
|
||||
// <Controller
|
||||
// control={control}
|
||||
// name={`blocks.${index}.content`}
|
||||
// render={({ field: { onChange, value } }) => (
|
||||
// <Editor
|
||||
// id={item.id}
|
||||
// onEditorChange={onChange}
|
||||
// value={value}
|
||||
// onInit={(_, editor) => {
|
||||
// editorRef.current = editor;
|
||||
// }}
|
||||
// init={{
|
||||
// content_style:
|
||||
// 'body {color: #fff; background: #14161f; font-size:16px;display:grid;grid-template-columns:repeat(4,1fr);} body > * {grid-column-start: 2;grid-column-end:4',
|
||||
// height: '100%',
|
||||
// font_size_formats: '10px 12px 14px 16px 18px 20px 24px 28px 30px',
|
||||
// video_template_callback: (data: { source: string }) =>
|
||||
// '<video src="' +
|
||||
// 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) => {
|
||||
// const formData = new FormData();
|
||||
// formData.append('files', blobInfo.blob(), blobInfo.filename());
|
||||
// formData.append('dest', 'blog');
|
||||
// const res = await api
|
||||
// .post('upload', { body: formData })
|
||||
// .json<{ files: string[] }>();
|
||||
// return process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0];
|
||||
// },
|
||||
// file_picker_types: 'image media',
|
||||
// file_picker_callback: async (cb) => {
|
||||
// videoUploadRef.current!.onchange = async function () {
|
||||
// const reader = new FileReader();
|
||||
// const file = videoUploadRef.current?.files?.[0];
|
||||
// reader.onload = async function () {
|
||||
// const id = 'blobId' + new Date().getTime();
|
||||
// const blobCache = editorRef.current?.editorUpload.blobCache;
|
||||
// const base64 = reader.result?.toString().split(',')[1];
|
||||
// const blobInfo = blobCache?.create(id, file!, base64!);
|
||||
// blobCache?.add(blobInfo!);
|
||||
// const formData = new FormData();
|
||||
// formData.append(
|
||||
// 'files',
|
||||
// blobInfo?.blob()!,
|
||||
// blobInfo?.filename()
|
||||
// );
|
||||
// formData.append('dest', 'blog');
|
||||
// const res = await api
|
||||
// .post('upload', { body: formData })
|
||||
// .json<{ files: string[] }>();
|
||||
|
||||
cb(process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0], {
|
||||
title: file?.name,
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file!);
|
||||
};
|
||||
videoUploadRef.current!.click();
|
||||
},
|
||||
automatic_uploads: true,
|
||||
plugins: [
|
||||
'anchor',
|
||||
'autolink',
|
||||
'charmap',
|
||||
'codesample',
|
||||
'emoticons',
|
||||
'image',
|
||||
'link',
|
||||
'lists',
|
||||
'media',
|
||||
'nonbreaking',
|
||||
'preview',
|
||||
'save',
|
||||
'searchreplace',
|
||||
'table',
|
||||
'visualblocks',
|
||||
'wordcount',
|
||||
],
|
||||
toolbar:
|
||||
'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat',
|
||||
tinycomments_mode: 'embedded',
|
||||
tinycomments_author: 'Author name',
|
||||
mergetags_list: [
|
||||
{ value: 'First.Name', title: 'First Name' },
|
||||
{ value: 'Email', title: 'Email' },
|
||||
],
|
||||
}}
|
||||
licenseKey="gpl"
|
||||
apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
accept="media/*"
|
||||
className="invisible"
|
||||
ref={videoUploadRef}
|
||||
/>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setModal(null, '');
|
||||
}}
|
||||
className="absolute top-4 right-4 z-[2]"
|
||||
>
|
||||
<Icon name="close" color="black" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// cb(process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0], {
|
||||
// title: file?.name,
|
||||
// });
|
||||
// };
|
||||
// reader.readAsDataURL(file!);
|
||||
// };
|
||||
// videoUploadRef.current!.click();
|
||||
// },
|
||||
// automatic_uploads: true,
|
||||
// plugins: [
|
||||
// 'anchor',
|
||||
// 'autolink',
|
||||
// 'charmap',
|
||||
// 'codesample',
|
||||
// 'emoticons',
|
||||
// 'image',
|
||||
// 'link',
|
||||
// 'lists',
|
||||
// 'media',
|
||||
// 'nonbreaking',
|
||||
// 'preview',
|
||||
// 'save',
|
||||
// 'searchreplace',
|
||||
// 'table',
|
||||
// 'visualblocks',
|
||||
// 'wordcount',
|
||||
// ],
|
||||
// toolbar:
|
||||
// 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat',
|
||||
// tinycomments_mode: 'embedded',
|
||||
// tinycomments_author: 'Author name',
|
||||
// mergetags_list: [
|
||||
// { value: 'First.Name', title: 'First Name' },
|
||||
// { value: 'Email', title: 'Email' },
|
||||
// ],
|
||||
// }}
|
||||
// licenseKey="gpl"
|
||||
// apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY}
|
||||
// />
|
||||
// )}
|
||||
// />
|
||||
// <input
|
||||
// type="file"
|
||||
// accept="media/*"
|
||||
// className="invisible"
|
||||
// ref={videoUploadRef}
|
||||
// />
|
||||
// <button
|
||||
// onClick={(e) => {
|
||||
// e.preventDefault();
|
||||
// setModal(null, '');
|
||||
// }}
|
||||
// className="absolute top-4 right-4 z-[2]"
|
||||
// >
|
||||
// <Icon name="close" color="black" />
|
||||
// </button>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ArticleHeader } from '@/components/ArticleHeader';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import parse from 'html-react-parser';
|
||||
import { ArticleSlider } from './ArticleSlider';
|
||||
@@ -12,7 +11,7 @@ export function ArticleContent({
|
||||
}: IArticle) {
|
||||
return (
|
||||
<>
|
||||
<ArticleHeader {...{ createdAt, cardImage, tags, title }} />
|
||||
{/* <ArticleHeader {...{ createdAt, cardImage, tags, title }} /> */}
|
||||
<div className="space-y-20 lg:pt-20 pt-14 lg:grid grid-cols-4 gap-x-4">
|
||||
{blocks.map((block, index) =>
|
||||
block.type === 'Content' ? (
|
||||
|
||||
@@ -11,7 +11,6 @@ export function RelevantArticle({
|
||||
id,
|
||||
tags,
|
||||
createdAt,
|
||||
description,
|
||||
relevantTags,
|
||||
}: IArticle & { relevantTags: string[] }) {
|
||||
const [year, month, date] = new Date(createdAt)
|
||||
@@ -44,11 +43,6 @@ export function RelevantArticle({
|
||||
<h3 className="h3 font-medium [-webkit-line-clamp:3] [-webkit-box-orient:vertical] [display:-webkit-box] text-ellipsis overflow-hidden">
|
||||
{title}
|
||||
</h3>
|
||||
{description && (
|
||||
<p className="m-text font-medium [-webkit-line-clamp:3] [-webkit-box-orient:vertical] [display:-webkit-box] overflow-hidden text-ellipsis">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="l-caption uppercase font-medium">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { MouseEvent, useRef, useState } from 'react';
|
||||
'use client';
|
||||
|
||||
import { MouseEvent, Suspense, useRef, useState } from 'react';
|
||||
import DotsIcon from '../../../../../public/icons/dots.svg';
|
||||
|
||||
export function ConsultationRange({
|
||||
consultations,
|
||||
@@ -17,7 +19,6 @@ export function ConsultationRange({
|
||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||
|
||||
function handleMouseDown(e: MouseEvent) {
|
||||
console.log(e.clientX - root.current!.getBoundingClientRect().x);
|
||||
setIsMouseDown(true);
|
||||
setStart(
|
||||
Math.max(
|
||||
@@ -71,12 +72,9 @@ export function ConsultationRange({
|
||||
onMouseLeave={handleMouseUp}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<Icon
|
||||
name="dots"
|
||||
size={24}
|
||||
color="#7A7A7A"
|
||||
svgProp={{ className: 'select-none' }}
|
||||
/>
|
||||
<Suspense fallback={<></>}>
|
||||
<DotsIcon className="w-6 h-6 text-[#7a7a7a] select-none" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
<p className="self-center right-8 font-medium text-[#7A7A7A] z-[1] btnl">
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import regionsData from '@/consts/regionsData.json';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { useRef, useState } from 'react';
|
||||
import { LazySvg } from '@/ui/LazySvg';
|
||||
import { Suspense, useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import ChevronDownIcon from '../../../../../public/icons/chevron_down.svg';
|
||||
|
||||
export interface Region {
|
||||
id: number;
|
||||
@@ -26,19 +29,23 @@ export function RegionSelector({
|
||||
useOnClickOutside([dropdownRef, root], () => setOpened(false));
|
||||
|
||||
return (
|
||||
<div className="md:space-y-3 max-md:order-2 md:max-lg:flex-1 relative select-none">
|
||||
<p className="font-medium text-[#7A7A7A] btnl max-md:hidden">Регион</p>
|
||||
<div className="max-md:order-2 md:max-lg:flex-1 relative select-none">
|
||||
<p className="font-medium text-[#7A7A7A] btnl max-md:hidden mb-3">
|
||||
Регион
|
||||
</p>
|
||||
<div
|
||||
ref={root}
|
||||
className="px-8 lg:py-6 py-4 bg-[#37393B99] rounded-2xl flex items-center justify-between lg:w-[360px]"
|
||||
onClick={() => setOpened(!opened)}
|
||||
onClick={() => setOpened((prev) => !prev)}
|
||||
>
|
||||
<p className="lg:btnl btnl font-medium">{chosen.name}</p>
|
||||
<Icon
|
||||
name={opened ? 'chevron_up' : 'chevron_down'}
|
||||
color="white"
|
||||
size={24}
|
||||
/>
|
||||
<div
|
||||
className={
|
||||
'transition-transform ' + (opened ? 'rotate-180' : 'rotate-0')
|
||||
}
|
||||
>
|
||||
<ChevronDownIcon className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
{opened && (
|
||||
<div
|
||||
@@ -46,7 +53,7 @@ export function RegionSelector({
|
||||
className="bg-[#37393B99] absolute z-10 w-full p-2 backdrop-blur-2xl mt-1 rounded-2xl max-h-96 overflow-y-auto"
|
||||
>
|
||||
{(regionsData as Region[]).map((region, index) => (
|
||||
<p
|
||||
<div
|
||||
key={index}
|
||||
className="hover:backdrop-blur-2xl rounded-xl lg:btnl btnm flex items-center justify-between w-full px-8 py-6 font-medium"
|
||||
onClick={() => {
|
||||
@@ -55,10 +62,20 @@ export function RegionSelector({
|
||||
}}
|
||||
>
|
||||
{region.name}
|
||||
{chosen.name === region.name && (
|
||||
<Icon name="check" color="white" />
|
||||
)}
|
||||
</p>
|
||||
<div className="relative w-6 h-6">
|
||||
{chosen.name === region.name && (
|
||||
<Suspense fallback={<div className="w-6 h-6 absolute" />}>
|
||||
<LazySvg
|
||||
name="check"
|
||||
width={24}
|
||||
height={24}
|
||||
color="white"
|
||||
className="absolute top-0 left-0"
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function ProjectsSlider({
|
||||
setCurrent((prev) => Math.min(projects.length, prev + 1))
|
||||
}
|
||||
>
|
||||
<Icon name="arrow_right" color="white" size={16} />
|
||||
{/* <Icon name="arrow_right" color="white" size={16} /> */}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,14 +18,14 @@ export function Engine({ slide }: { slide?: number }) {
|
||||
</p>
|
||||
<div className="flex-nowrap flex gap-2">
|
||||
{engine.map((item) => (
|
||||
<SlideBadge {...item} key={item.title} className="pr-6" />
|
||||
<SlideBadge {...item} key={item.title} className="lg:pr-6" />
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="md:space-y-6 space-y-7 md:h-[348px] max-md:bg-radial-[at_100%_100%] before:absolute before:max-md:w-full before:h-full before:top-0 before:left-0 before:-z-1 before:bg-[#0F1011] before:bg-radial-[at_100%_100%] before:from-[#7a7a7a66] before:rounded-2xl max-md:px-4 max-md:py-6 max-md:rounded-2xl from-[#7A7A7A66] max-md:backdrop-blur-[500px] overflow-hidden sticky md:top-[calc(100vh-388px)] top-100 md:bg-[#0F1011]">
|
||||
<div className="md:space-y-6 space-y-7 md:h-[348px] max-md:bg-radial-[at_100%_100%] before:absolute before:max-md:w-full before:h-full before:top-0 before:left-0 before:-z-1 before:bg-[#0F1011] before:bg-radial-[at_100%_100%] before:from-[#7a7a7a66] before:rounded-2xl max-md:px-4 max-md:py-6 max-md:rounded-2xl from-[#7A7A7A66] max-md:backdrop-blur-[500px] overflow-hidden sticky md:top-[calc(100vh-388px)] top-120 md:bg-[#0F1011] h-[340px]">
|
||||
<p className="heading2 md:text-center font-medium">
|
||||
Модуль инженерных систем
|
||||
</p>
|
||||
@@ -36,11 +36,11 @@ export function Engine({ slide }: { slide?: number }) {
|
||||
key={title}
|
||||
>
|
||||
<p className="text1 max-md:hidden">{title}</p>
|
||||
<div className="relative md:w-[calc(216/240*100%)] max-md:max-w-16 md:self-end">
|
||||
<div className="relative md:max-lg:w-[calc(216/240*100%)] max-md:max-w-16 md:self-end">
|
||||
<Image
|
||||
src={src}
|
||||
fill
|
||||
alt={title}
|
||||
fill
|
||||
className="rounded-xl md:self-end !relative w-full h-full max-md:aspect-square"
|
||||
sizes="100%"
|
||||
/>
|
||||
|
||||
@@ -49,7 +49,7 @@ export function Infrastructure({ slide }: { slide?: number }) {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="rounded-2xl backdrop-blur-[500px] before:bg-[#0F1011] before:absolute before:w-full before:h-full before:top-0 before:left-0 before:-z-1 before:rounded-2xl before:bg-radial-[at_100%_100%] before:from-[#7A7A7A66] px-4 md:px-6 py-6 md:grid gap-7 space-y-7 grid-rows-[5fr_2fr] grid-cols-2 md:h-[348px] sticky md:top-[calc(100vh-388px)] top-100">
|
||||
<div className="rounded-2xl backdrop-blur-[500px] before:bg-[#0F1011] before:absolute before:w-full before:h-full before:top-0 before:left-0 before:-z-1 before:rounded-2xl before:bg-radial-[at_100%_100%] before:from-[#7A7A7A66] px-4 md:px-6 py-6 md:grid gap-7 space-y-7 grid-rows-[5fr_2fr] grid-cols-2 md:h-[348px] sticky md:top-[calc(100vh-388px)] top-120 h-[340px] overflow-hidden">
|
||||
<div className="max-md:space-y-4 gap-7 md:grid grid-cols-2 col-span-2">
|
||||
<p className="heading2 font-medium">
|
||||
Демонстрация инфраструктуры вокруг будущего комплекса
|
||||
@@ -58,7 +58,7 @@ export function Infrastructure({ slide }: { slide?: number }) {
|
||||
Режим «Инфраструктура» знакомит пользователя с перспективной
|
||||
застройкой целого района. В зависимости от срока сдачи либо
|
||||
функциональной нагрузки того или иного блока можно ввести выделение
|
||||
цветом.
|
||||
цветом
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex col-span-2 gap-1 md:gap-[5px]">
|
||||
|
||||
@@ -36,7 +36,7 @@ export function Insolation({ slide }: { slide?: number }) {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="rounded-2xl backdrop-blur-[500px] bg-radial-[at_100%_100%] before:from-[#7a7a7a66] before:rounded-2xl before:absolute before:w-full before:h-full before:top-0 before:left-0 before:bg-[#0F1011] before:bg-radial-[at_100%_100%] before:-z-1 overflow-hidden md:bg-radial-[at_50%_0%] from-[#7A7A7A66] md:px-6 py-6 px-4 max-md:space-y-7 md:grid gap-y-[30px] grid-cols-2 grid-rows-[1fr_3fr] md:h-[348px] sticky md:top-[calc(100vh-388px)] top-100">
|
||||
<div className="rounded-2xl backdrop-blur-[500px] bg-radial-[at_100%_100%] before:from-[#7a7a7a66] before:rounded-2xl before:absolute before:w-full before:h-full before:top-0 before:left-0 before:bg-[#0F1011] before:bg-radial-[at_100%_100%] before:-z-1 overflow-hidden md:bg-radial-[at_50%_0%] from-[#7A7A7A66] md:px-6 py-6 px-4 max-md:space-y-7 md:grid gap-y-[30px] grid-cols-2 grid-rows-[1fr_3fr] md:h-[348px] sticky md:top-[calc(100vh-388px)] top-120 h-[340px]">
|
||||
<div className="md:grid max-md:space-y-4 grid-cols-2 col-span-2 gap-1">
|
||||
<p className="heading2 font-medium">Интерактивная инсоляция</p>
|
||||
<p className="text1">
|
||||
|
||||
@@ -69,7 +69,7 @@ export function IntegrationCRM({ slide }: { slide?: number }) {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="rounded-2xl backdrop-blur-[500px] md:bg-[#0F1011] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:-z-1 before:bg-[#0f1011] before:bg-radial-[at_100%_100%] before:from-[#7a7a7a66] md:bg-radial-[at_50%_0%] bg-radial-[at_100%_100%] overflow-hidden max-md:space-y-7 from-[#7A7A7A66] md:px-6 px-4 py-6 md:grid grid-cols-2 md:h-[348px] sticky md:top-[calc(100vh-388px)] top-100">
|
||||
<div className="rounded-2xl backdrop-blur-[500px] md:bg-[#0F1011] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:-z-1 before:bg-[#0f1011] before:bg-radial-[at_100%_100%] before:from-[#7a7a7a66] md:bg-radial-[at_50%_0%] bg-radial-[at_100%_100%] overflow-hidden max-md:space-y-7 from-[#7A7A7A66] md:px-6 px-4 py-6 md:grid grid-cols-2 md:h-[348px] sticky md:top-[calc(100vh-388px)] h-[340px] top-120">
|
||||
<p className="heading2 font-medium">
|
||||
Интеграция с CRM-системой застройщика
|
||||
</p>
|
||||
|
||||
@@ -42,8 +42,8 @@ export function PresentationMini() {
|
||||
}, [slide, videoRefs]);
|
||||
|
||||
return (
|
||||
<div className="mt-25">
|
||||
<div className="md:top-25 -top-25 md:space-y-12 sticky space-y-10">
|
||||
<div className="mt-25 lg:hidden">
|
||||
<div className="top-20 md:space-y-12 sticky space-y-10">
|
||||
<Title>
|
||||
Интерактивная презентация{' '}
|
||||
<span className="text-gradient">
|
||||
@@ -77,6 +77,7 @@ export function PresentationMini() {
|
||||
<Insolation />
|
||||
<Engine />
|
||||
<IntegrationCRM />
|
||||
<div className="h-[50vh] bg-[#0F1011]" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Icon } from '@/ui/Icon';
|
||||
export function SearchAndSelect({ slide }: { slide?: number }) {
|
||||
return (
|
||||
<div
|
||||
className={`max-md:p-6 flex max-md:flex-col lg:flex-col lg:h-full md:gap-3 gap-y-7 lg:z-10 lg:max-w-[33vw] md:h-[348px] lg:transition-all max-md:rounded-2xl max-lg:sticky md:max-lg:top-[calc(100vh-388px)] max-md:top-100 lg:duration-1000 lg:col-span-2 select-none max-md:bg-radial-[at_100%_100%] from-[#7A7A7A66] max-md:backdrop-blur-[500px]${
|
||||
className={`max-md:p-6 flex max-md:flex-col lg:flex-col lg:h-full md:gap-3 gap-y-7 lg:z-10 lg:max-w-[33vw] md:h-[348px] h-[340px] lg:transition-all max-md:rounded-2xl max-lg:sticky md:max-lg:top-[calc(100vh-388px)] max-md:top-120 lg:duration-1000 lg:col-span-2 select-none max-md:bg-radial-[at_100%_100%] from-[#7A7A7A66] max-md:backdrop-blur-[500px]${
|
||||
slide ? ' lg:absolute lg:translate-x-[33%] lg:opacity-0' : ''
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ThreeDTour({ slide }: { slide?: number }) {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="md:space-y-6 space-y-7 before:bg-[#0F1011] before:max-md:bg-radial-[at_100%_100%] before:w-full before:absolute before:rounded-2xl before:top-0 before:left-0 before:h-full before:-z-1 before:from-[#7A7A7A66] backdrop-blur-[500px] md:backdrop-blur-xs md:h-[348px] sticky max-md:px-4 max-md:py-6 max-md:rounded-2xl md:top-[calc(100vh-388px)] top-100">
|
||||
<div className="md:space-y-6 space-y-7 before:bg-[#0F1011] before:max-md:bg-radial-[at_100%_100%] before:w-full before:absolute before:rounded-2xl before:top-0 before:left-0 before:h-full before:-z-1 before:from-[#7A7A7A66] h-[340px] backdrop-blur-[500px] md:backdrop-blur-xs md:h-[348px] sticky max-md:px-4 max-md:py-6 max-md:rounded-2xl md:top-[calc(100vh-388px)] top-120">
|
||||
<p className="heading2 md:text-center font-medium">3D-тур</p>
|
||||
<div className="max-md:w-fit md:items-stretch md:gap-2 gap-x-3 gap-y-6 grid grid-cols-3">
|
||||
{threeDTour.map((item) => (
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Reviews() {
|
||||
<div className="max-lg:space-y-2">
|
||||
<div
|
||||
ref={ref}
|
||||
className="relative m-auto max-sm:aspect-[340/480] lg:space-y-20 lg:aspect-[1400/616] lg:min-w-[1040px] max-w-full sm:max-lg:aspect-[736/480] lg:mt-[140px] mt-[100px] max-lg:flex max-lg:flex-col transition-all group"
|
||||
className="relative m-auto max-sm:aspect-[340/480] lg:aspect-[1400/616] lg:min-w-[1040px] max-w-full sm:max-lg:aspect-[736/480] lg:mt-[140px] mt-[100px] max-lg:flex max-lg:flex-col transition-all group"
|
||||
style={{
|
||||
width: isLg
|
||||
? scroll < 500
|
||||
|
||||
@@ -37,7 +37,6 @@ export function Streaming() {
|
||||
const [point, setPoint] = useState([0, 0]);
|
||||
const [muted, setMuted] = useState(true);
|
||||
const [playing, setPlaying] = useState(true);
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
|
||||
function handleProgressbarClick(e: MouseEvent) {
|
||||
videoRef.current!.currentTime =
|
||||
@@ -125,10 +124,10 @@ export function Streaming() {
|
||||
className="lg:aspect-[1400/640] sm:aspect-[736/480] aspect-[340/600] rounded-2xl w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute left-0 top-0 h-5/6 w-full z-[1]">
|
||||
<div ref={ref} className="relative w-full h-full">
|
||||
<div ref={ref} className="relative w-full h-full group">
|
||||
<button
|
||||
ref={btnRef}
|
||||
className="bg-[#37393B99] p-[50px] backdrop-blur-[30.72px] rounded-full in-hover:opacity-100 transition-opacity in-hover:cursor-none opacity-0 sticky"
|
||||
className="bg-[#37393B99] p-[50px] backdrop-blur-[30.72px] rounded-full group-hover:opacity-100 transition-opacity group-hover:cursor-none opacity-0 sticky outline-none"
|
||||
style={{ left: point[0] - 64, top: point[1] - 64 }}
|
||||
onClick={handleVideoClick}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function AwardsCard({ className }: { className?: string }) {
|
||||
<Image
|
||||
src={'/img/components/header/show_case_card.png'}
|
||||
fill
|
||||
sizes="calc(225/361*100%)"
|
||||
sizes="100%"
|
||||
alt="upside"
|
||||
className="object-cover !relative"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { IProject } from '@/types/IProject';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AwardsCard } from './AwardsCard';
|
||||
@@ -30,7 +29,7 @@ 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"
|
||||
>
|
||||
Смотреть все
|
||||
<Icon name="arrow_more" color="white" size={20} />
|
||||
{/* <Icon name="arrow_more" color="white" size={20} /> */}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import { postTags } from '@/consts/postTags';
|
||||
import { projectsTags } from '@/consts/projectsTags';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { LazySvg } from '@/ui/LazySvg';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Suspense, useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { TagFilter } from './TagsFilter';
|
||||
|
||||
@@ -90,7 +91,9 @@ export function TagsFilters({
|
||||
className="gap-x-2 rounded-2xl flex items-center pl-6 p-4 bg-[#37393B99] backdrop-blur-sm"
|
||||
>
|
||||
<p className="btnm font-medium">Фильтры</p>
|
||||
<Icon name={'filters'} color="white" />
|
||||
<Suspense fallback={<div className="w-4 h-4" />}>
|
||||
<LazySvg name="filters" color="white" width={16} height={16} />
|
||||
</Suspense>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,14 +9,11 @@ export function useDynamicSvgImport(iconName: string) {
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
// dynamically import the mentioned svg icon name in props
|
||||
const importSvgIcon = async (): Promise<void> => {
|
||||
// please make sure all your svg icons are placed in the same directory
|
||||
// if we want that part to be configurable then instead of iconName we will send iconPath as prop
|
||||
try {
|
||||
importedIconRef.current = (
|
||||
await import(`../../public/icons/${iconName}.svg`)
|
||||
).default; // svgr provides ReactComponent for given svg path
|
||||
).default;
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
console.error(err);
|
||||
|
||||
@@ -15,25 +15,25 @@ export function useMediaQueries(lg = 1024, md = 768, sm = 640) {
|
||||
const handler = (e: MediaQueryListEvent) => setIsLg(e.matches);
|
||||
lgMedia.addEventListener('change', handler);
|
||||
return () => lgMedia.removeEventListener('change', handler);
|
||||
}, []);
|
||||
}, [lgMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MediaQueryListEvent) => setIsMd(e.matches);
|
||||
mdMedia.addEventListener('change', handler);
|
||||
return () => mdMedia.removeEventListener('change', handler);
|
||||
}, []);
|
||||
}, [mdMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MediaQueryListEvent) => setIsSm(e.matches);
|
||||
smMedia.addEventListener('change', handler);
|
||||
return () => smMedia.removeEventListener('change', handler);
|
||||
}, []);
|
||||
}, [smMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MediaQueryListEvent) => setIsXs(e.matches);
|
||||
xsMedia.addEventListener('change', handler);
|
||||
return () => xsMedia.removeEventListener('change', handler);
|
||||
}, []);
|
||||
}, [xsMedia]);
|
||||
|
||||
return { isLg, isMd, isSm, isXs };
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,10 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { motion, useInView, useMotionValue, useSpring } from 'framer-motion';
|
||||
import { Manrope } from 'next/font/google';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const manrope = Manrope({ subsets: ['latin'] });
|
||||
// const manrope = Manrope({ subsets: ['latin'] });
|
||||
|
||||
export function Figure({
|
||||
percent,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
interface LazySvgProps extends ComponentProps<'svg'> {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const LazySvg = async ({ name, ...props }: LazySvgProps) => {
|
||||
const Svg = dynamic(() => import('../../public/icons/' + name + '.svg'));
|
||||
|
||||
// Or without using `dynamic`:
|
||||
// We use `default` here because `@svgr/webpack` converts all other *.svg imports to React components, this might be different for other loaders.
|
||||
|
||||
return <Svg {...props} />;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ export function SeasonCard({
|
||||
className="!relative lg:min-w-full md:min-w-[calc(138/224*100%)]"
|
||||
alt={title}
|
||||
fill
|
||||
sizes="(min-width: 768px) calc(138/224*100%), (min-width: 1024px) 100%"
|
||||
sizes="100%"
|
||||
/>
|
||||
</div>
|
||||
<p className="text1 md:font-medium text-center">{title}</p>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function SlideBadge({
|
||||
alt={title}
|
||||
fill
|
||||
className="rounded-xl !relative"
|
||||
sizes="(min-width: 1024px) 48px, (min-width: 768px) 125px, 64px"
|
||||
sizes="100%"
|
||||
/>
|
||||
</div>
|
||||
<p className="text1">{title}</p>
|
||||
|
||||
Reference in New Issue
Block a user