upd
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.62.7",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
|
"@tanstack/react-query-devtools": "^5.64.2",
|
||||||
"@tinymce/tinymce-react": "^5.1.1",
|
"@tinymce/tinymce-react": "^5.1.1",
|
||||||
"countries-phone-masks": "^1.1.0",
|
"countries-phone-masks": "^1.1.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ArticleActions } from '@/components/ArticleActions';
|
|||||||
import { ArticleHeader } from '@/components/ArticleHeader';
|
import { ArticleHeader } from '@/components/ArticleHeader';
|
||||||
import { ArticleContentInput } from '@/components/articleInputs/ArticleContentInput';
|
import { ArticleContentInput } from '@/components/articleInputs/ArticleContentInput';
|
||||||
import { ArticleSliderInput } from '@/components/articleInputs/ArticleSliderInput';
|
import { ArticleSliderInput } from '@/components/articleInputs/ArticleSliderInput';
|
||||||
import { IArticleFormInput } from '@/components/modals/ArticleHeaderFormModal';
|
import { IArticleFormInput } from '@/components/modals/ArticleFormModal';
|
||||||
import { ArticleContent } from '@/components/pages/BlogPage/ArticleContent';
|
import { ArticleContent } from '@/components/pages/BlogPage/ArticleContent';
|
||||||
import { IArticle } from '@/types/IArticle';
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { Button } from '@/ui/Button';
|
import { Button } from '@/ui/Button';
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { api } from '@/api';
|
import { api } from '@/api';
|
||||||
|
import { CloseIcon } from '@/components/icons/CloseIcon';
|
||||||
import { RelevantArticlesPreview } from '@/components/pages/ArticlePage/RelevantArticlesPreview';
|
import { RelevantArticlesPreview } from '@/components/pages/ArticlePage/RelevantArticlesPreview';
|
||||||
|
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
||||||
import { IArticle } from '@/types/IArticle';
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
@@ -41,10 +44,19 @@ export default async function Layout({
|
|||||||
params: Promise<{ articleId: string }>;
|
params: Promise<{ articleId: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { articleId } = await params;
|
const { articleId } = await params;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="absolute w-screen h-screen bg-[#0F101199] backdrop-blur-lg">
|
<section className="fixed top-0 left-0 w-screen h-screen bg-[#0F101199] backdrop-blur-lg z-[14]">
|
||||||
<RelevantArticlesPreview articleId={articleId} />
|
<RelevantArticlesPreview articleId={articleId} />
|
||||||
{children}
|
{children}
|
||||||
|
<Link
|
||||||
|
href={'/blog'}
|
||||||
|
className="fixed right-5 top-5 rounded-2xl bg-[#37393B99] p-4"
|
||||||
|
>
|
||||||
|
<ClassNameWrapper className={'w-4 h-4'}>
|
||||||
|
<CloseIcon />
|
||||||
|
</ClassNameWrapper>
|
||||||
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { api } from '@/api';
|
import { api } from '@/api';
|
||||||
|
import { ArticleSyncPage } from '@/components/pages/ArticlePage/ArticleSyncPage';
|
||||||
import { IArticle } from '@/types/IArticle';
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { HydrationBoundary, QueryClient } from '@tanstack/react-query';
|
import {
|
||||||
|
dehydrate,
|
||||||
|
HydrationBoundary,
|
||||||
|
QueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
export default async function ArticlePage({
|
export default async function ArticlePage({
|
||||||
params: { articleId },
|
params: { articleId },
|
||||||
@@ -15,5 +20,9 @@ export default async function ArticlePage({
|
|||||||
await api.get(`articles/${articleId}`).json<IArticle>(),
|
await api.get(`articles/${articleId}`).json<IArticle>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return <HydrationBoundary queryClient={queryClient}></HydrationBoundary>;
|
return (
|
||||||
|
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||||
|
<ArticleSyncPage articleId={articleId} />
|
||||||
|
</HydrationBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ArticlesPageHeader } from '@/components/pages/BlogPage/ArticlesPageHeader';
|
import { Title } from '@/ui/Title';
|
||||||
import { Metadata, ResolvingMetadata } from 'next';
|
import { Metadata, ResolvingMetadata } from 'next';
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
@@ -16,9 +16,12 @@ export default async function BlogLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="space-y-12">
|
||||||
<ArticlesPageHeader />
|
<Title className="text-center row-start-1" headerLevel={2}>
|
||||||
{children}
|
В блоге собраны все публикации о работе компании:
|
||||||
|
<span className="text-[#7A7A7A]"> новости, статьи и видео</span>
|
||||||
|
</Title>
|
||||||
|
<div className="grid grid-cols-6">{children}</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { api } from '@/api';
|
import { api } from '@/api';
|
||||||
import { ArticlesList } from '@/components/pages/BlogPage/ArticlesList';
|
import { ArticlesList } from '@/components/pages/BlogPage/ArticlesList';
|
||||||
|
import { ArticlesPageActions } from '@/components/pages/BlogPage/ArticlesPageActions';
|
||||||
import { IArticle } from '@/types/IArticle';
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { HydrationBoundary, QueryClient } from '@tanstack/react-query';
|
import {
|
||||||
|
dehydrate,
|
||||||
|
HydrationBoundary,
|
||||||
|
QueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export default async function BlogPage({
|
export default async function BlogPage({
|
||||||
@@ -19,7 +24,8 @@ export default async function BlogPage({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HydrationBoundary queryClient={queryClient}>
|
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||||
|
<ArticlesPageActions tags={tags} />
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<ArticlesList tags={tags} />
|
<ArticlesList tags={tags} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { usePathname, useRouter } from 'next/navigation';
|
|||||||
import { UseFormSetValue } from 'react-hook-form';
|
import { UseFormSetValue } from 'react-hook-form';
|
||||||
import { DeleteIcon } from './icons/DeleteIcon';
|
import { DeleteIcon } from './icons/DeleteIcon';
|
||||||
import { EditIcon } from './icons/EditIcon';
|
import { EditIcon } from './icons/EditIcon';
|
||||||
import {
|
import { ArticleFormModal, IArticleFormInput } from './modals/ArticleFormModal';
|
||||||
ArticleFormModal,
|
|
||||||
IArticleFormInput,
|
|
||||||
} from './modals/ArticleHeaderFormModal';
|
|
||||||
import { DeleteArticleModal } from './modals/DeleteArticleModal';
|
import { DeleteArticleModal } from './modals/DeleteArticleModal';
|
||||||
|
|
||||||
export function ArticleActions({
|
export function ArticleActions({
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { EditIcon } from './icons/EditIcon';
|
|||||||
import { PlusIcon } from './icons/PlusIcon';
|
import { PlusIcon } from './icons/PlusIcon';
|
||||||
import { TrashIcon } from './icons/TrashIcon';
|
import { TrashIcon } from './icons/TrashIcon';
|
||||||
import { ArticleContentEditorModal } from './modals/ArticleContentEditorModal';
|
import { ArticleContentEditorModal } from './modals/ArticleContentEditorModal';
|
||||||
import { IArticleFormInput } from './modals/ArticleHeaderFormModal';
|
import { IArticleFormInput } from './modals/ArticleFormModal';
|
||||||
|
|
||||||
interface IBlockActionsProps {
|
interface IBlockActionsProps {
|
||||||
item: IContent & Record<'id', string>;
|
item: IContent & Record<'id', string>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function DeleteItemModal({
|
|||||||
id,
|
id,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
entity: 'projects' | 'companies';
|
entity: 'projects' | 'companies' | 'articles';
|
||||||
id: string;
|
id: string;
|
||||||
}) {
|
}) {
|
||||||
const { setModal } = useModalStore();
|
const { setModal } = useModalStore();
|
||||||
@@ -27,9 +27,13 @@ export function DeleteItemModal({
|
|||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onClick={remove} className="self-end text-white outline-none">
|
<Button onClick={remove} className="self-end text-white outline-none">
|
||||||
Удалить {entity === 'projects' ? 'проект' : 'компанию'}
|
Удалить{' '}
|
||||||
|
{entity === 'projects'
|
||||||
|
? 'проект'
|
||||||
|
: entity === 'companies'
|
||||||
|
? 'компанию'
|
||||||
|
: 'статью'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,50 +3,80 @@
|
|||||||
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
||||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||||
import { useModalStore } from '@/stores/useModalStore';
|
import { useModalStore } from '@/stores/useModalStore';
|
||||||
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { ICompany } from '@/types/ICompany';
|
import { ICompany } from '@/types/ICompany';
|
||||||
import { IProject } from '@/types/IProject';
|
import { IProject } from '@/types/IProject';
|
||||||
|
import { isCompany } from '@/utils/isCompany';
|
||||||
import { isProject } from '@/utils/isProject';
|
import { isProject } from '@/utils/isProject';
|
||||||
|
import { SyntheticEvent } from 'react';
|
||||||
import { DeleteItemModal } from './DeleteItemModal';
|
import { DeleteItemModal } from './DeleteItemModal';
|
||||||
import { DeleteIcon } from './icons/DeleteIcon';
|
import { DeleteIcon } from './icons/DeleteIcon';
|
||||||
import { EditIcon } from './icons/EditIcon';
|
import { EditIcon } from './icons/EditIcon';
|
||||||
|
import { ArticleFormModal } from './modals/ArticleFormModal';
|
||||||
import { CompanyFormModal } from './modals/CompanyFormModal';
|
import { CompanyFormModal } from './modals/CompanyFormModal';
|
||||||
import { ProjectFormModal } from './modals/ProjectFormModal';
|
import { ProjectFormModal } from './modals/ProjectFormModal';
|
||||||
|
|
||||||
export function ItemActions({ item }: { item: IProject | ICompany }) {
|
export function ItemActions({
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
item: IProject | ICompany | IArticle;
|
||||||
|
}) {
|
||||||
const { data: auth } = useCheckAuthQuery();
|
const { data: auth } = useCheckAuthQuery();
|
||||||
|
|
||||||
const { setModal } = useModalStore();
|
const { setModal } = useModalStore();
|
||||||
|
|
||||||
function handleEdit() {
|
function handleEdit(e: SyntheticEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
if (isProject(item)) {
|
if (isProject(item)) {
|
||||||
const { company, ...project } = item;
|
const { company, ...project } = item;
|
||||||
setModal(
|
setModal(
|
||||||
<ProjectFormModal action="edit" defaultValues={project} />,
|
<ProjectFormModal action="edit" defaultValues={project} />,
|
||||||
'editProjectForm'
|
'editProjectForm'
|
||||||
);
|
);
|
||||||
} else {
|
} else if (isCompany(item)) {
|
||||||
const { projects, ...company } = item;
|
const { projects, ...company } = item;
|
||||||
setModal(
|
setModal(
|
||||||
<CompanyFormModal action="edit" defaultValues={company} />,
|
<CompanyFormModal action="edit" defaultValues={company} />,
|
||||||
'editCompanyForm'
|
'editCompanyForm'
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
setModal(
|
||||||
|
<ArticleFormModal action="edit" defaultValues={item} />,
|
||||||
|
'editArticleForm'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete(e: SyntheticEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
setModal(
|
setModal(
|
||||||
<DeleteItemModal
|
<DeleteItemModal
|
||||||
id={item.id}
|
id={item.id}
|
||||||
title={'Удаление ' + (isProject(item) ? 'проекта' : 'компании')}
|
title={
|
||||||
entity={isProject(item) ? 'projects' : 'companies'}
|
'Удаление ' +
|
||||||
|
(isProject(item)
|
||||||
|
? 'проекта'
|
||||||
|
: isCompany(item)
|
||||||
|
? 'компании'
|
||||||
|
: 'статьи')
|
||||||
|
}
|
||||||
|
entity={
|
||||||
|
isProject(item)
|
||||||
|
? 'projects'
|
||||||
|
: isCompany(item)
|
||||||
|
? 'companies'
|
||||||
|
: 'articles'
|
||||||
|
}
|
||||||
/>,
|
/>,
|
||||||
`delete${isProject(item) ? 'Project' : 'Company'}`
|
`delete${
|
||||||
|
isProject(item) ? 'Project' : isCompany(item) ? 'Company' : 'Article'
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
auth && (
|
auth && (
|
||||||
<div className="absolute top-0 left-0 p-4 flex opacity-0 gap-1 z-[5] group-hover:opacity-100 transition-opacity">
|
<div className="absolute top-0 left-0 p-4 flex opacity-0 gap-1 z-[6] group-hover:opacity-100 transition-opacity">
|
||||||
<button
|
<button
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
className="relative px-3 py-2 bg-[#37393B99] backdrop-blur-sm rounded-full outline-none"
|
className="relative px-3 py-2 bg-[#37393B99] backdrop-blur-sm rounded-full outline-none"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
UseFormWatch,
|
UseFormWatch,
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
import { BlockActions } from '../BlockActions';
|
import { BlockActions } from '../BlockActions';
|
||||||
import { IArticleFormInput } from '../modals/ArticleHeaderFormModal';
|
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||||
|
|
||||||
export interface IArticleContentInputProps {
|
export interface IArticleContentInputProps {
|
||||||
item: IContent & Record<'id', string>;
|
item: IContent & Record<'id', string>;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SyntheticEvent } from 'react';
|
|||||||
import { UseFieldArrayRemove, UseFormSetValue } from 'react-hook-form';
|
import { UseFieldArrayRemove, UseFormSetValue } from 'react-hook-form';
|
||||||
import { CloseIcon } from '../icons/CloseIcon';
|
import { CloseIcon } from '../icons/CloseIcon';
|
||||||
import { MediaUploader } from '../MediaUploader';
|
import { MediaUploader } from '../MediaUploader';
|
||||||
import { IArticleFormInput } from '../modals/ArticleHeaderFormModal';
|
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||||
|
|
||||||
interface IArticleSliderImageInputProps {
|
interface IArticleSliderImageInputProps {
|
||||||
setValue: UseFormSetValue<IArticleFormInput>;
|
setValue: UseFormSetValue<IArticleFormInput>;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
import { PlusIcon } from '../icons/PlusIcon';
|
import { PlusIcon } from '../icons/PlusIcon';
|
||||||
import { TrashIcon } from '../icons/TrashIcon';
|
import { TrashIcon } from '../icons/TrashIcon';
|
||||||
import { IArticleFormInput } from '../modals/ArticleHeaderFormModal';
|
import { IArticleFormInput } from '../modals/ArticleFormModal';
|
||||||
import { ArticleSliderImageInput } from './ArticleSliderImageInput';
|
import { ArticleSliderImageInput } from './ArticleSliderImageInput';
|
||||||
|
|
||||||
interface IArticleSliderInputProps {
|
interface IArticleSliderInputProps {
|
||||||
|
|||||||
@@ -6,22 +6,18 @@ import { useRef } from 'react';
|
|||||||
import { Control, Controller } from 'react-hook-form';
|
import { Control, Controller } from 'react-hook-form';
|
||||||
import { Editor } from 'tinymce';
|
import { Editor } from 'tinymce';
|
||||||
import { EditIcon } from '../icons/EditIcon';
|
import { EditIcon } from '../icons/EditIcon';
|
||||||
import { IArticleFormInput } from './ArticleHeaderFormModal';
|
|
||||||
|
|
||||||
export default function ArticleContentFormModal({
|
export default function ArticleContentFormModal({
|
||||||
drafted,
|
|
||||||
cardImage,
|
|
||||||
createdAt,
|
|
||||||
id,
|
|
||||||
posterImage,
|
posterImage,
|
||||||
tags,
|
tags,
|
||||||
title,
|
title,
|
||||||
control,
|
control,
|
||||||
}: IArticleFormInput & { control: Control<IArticle> }) {
|
blocks,
|
||||||
|
}: IArticle & { control: Control<IArticle> }) {
|
||||||
const ref = useRef<Editor | null>(null);
|
const ref = useRef<Editor | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] max-h-[calc(100vh-40px)] pl-[75px] pr-[55px] overflow-y-auto">
|
<div className="relative space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] max-h-[calc(100vh-40px)] z-[10] pl-[75px] pr-[55px] overflow-y-auto">
|
||||||
<Button
|
<Button
|
||||||
className="absolute top-3 right-4 bg-[#37393B99] backdrop-blur-sm p-4 btnm"
|
className="absolute top-3 right-4 bg-[#37393B99] backdrop-blur-sm p-4 btnm"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -51,27 +47,29 @@ export default function ArticleContentFormModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-10">
|
<div className="pt-10">
|
||||||
<Controller
|
{blocks.map((block, index) => (
|
||||||
name="blocks"
|
<Controller
|
||||||
control={control}
|
key={index}
|
||||||
render={({ field: { onChange, value } }) => (
|
name={`blocks.${index}`}
|
||||||
<BundledEditor
|
defaultValue={block}
|
||||||
onChange={onChange}
|
control={control}
|
||||||
value={
|
render={({ field: { onChange, value } }) => (
|
||||||
value[0].type === 'Content'
|
<BundledEditor
|
||||||
? value[0].content
|
onChange={onChange}
|
||||||
: value[0].images[0].img
|
value={
|
||||||
}
|
value.type === 'Content' ? value.content : value.images[0].img
|
||||||
onInit={(_, editor) => {
|
}
|
||||||
ref.current = editor;
|
onInit={(_, editor) => {
|
||||||
}}
|
ref.current = editor;
|
||||||
init={{
|
}}
|
||||||
content_style: 'body {background: #232425}',
|
init={{
|
||||||
}}
|
content_style: 'body {background: #232425}',
|
||||||
inline
|
}}
|
||||||
/>
|
inline
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+5
-4
@@ -10,8 +10,6 @@ import { MediaUploader } from '../MediaUploader';
|
|||||||
import ArticleContentFormModal from './ArticleContentFormModal';
|
import ArticleContentFormModal from './ArticleContentFormModal';
|
||||||
import { FormModalHeader } from './FormModalHeader';
|
import { FormModalHeader } from './FormModalHeader';
|
||||||
|
|
||||||
export interface IArticleFormInput extends Omit<IArticle, 'blocks'> {}
|
|
||||||
|
|
||||||
interface IArticleFormModalProps<TAction extends 'create' | 'edit'> {
|
interface IArticleFormModalProps<TAction extends 'create' | 'edit'> {
|
||||||
action: TAction;
|
action: TAction;
|
||||||
defaultValues?: TAction extends 'edit' ? IArticle : undefined;
|
defaultValues?: TAction extends 'edit' ? IArticle : undefined;
|
||||||
@@ -25,11 +23,14 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
|||||||
|
|
||||||
const { handleSubmit, register, setValue, getValues, control } =
|
const { handleSubmit, register, setValue, getValues, control } =
|
||||||
useForm<IArticle>({
|
useForm<IArticle>({
|
||||||
defaultValues: { ...defaultValues, blocks: [], drafted: true },
|
defaultValues: { ...defaultValues, blocks: [], tags: [], drafted: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSubmit(data: IArticle) {
|
async function onSubmit(data: IArticle) {
|
||||||
const article = await mutateAsync(data);
|
const article = await mutateAsync({
|
||||||
|
...data,
|
||||||
|
blocks: JSON.stringify(data.blocks),
|
||||||
|
});
|
||||||
setModal(
|
setModal(
|
||||||
<ArticleContentFormModal {...article} control={control} />,
|
<ArticleContentFormModal {...article} control={control} />,
|
||||||
'articleContentFormModal'
|
'articleContentFormModal'
|
||||||
@@ -1,5 +1,41 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export function ArticleSyncPage() {
|
import { useGetArticleById } from '@/queries/getArticleById';
|
||||||
return <div></div>;
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
export function ArticleSyncPage({ articleId }: { articleId: string }) {
|
||||||
|
const { data: article } = useGetArticleById(articleId);
|
||||||
|
|
||||||
|
if (!article) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-[calc(100vh-40px)] lg:w-[calc(954/1440*100vw)] top-5 rounded-[28px] bg-[#232425] m-auto overflow-hidden">
|
||||||
|
<div className="w-full relative">
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
<Image
|
||||||
|
className="!relative object-cover lg:max-h-[calc(261/731*(100vh-40px))]"
|
||||||
|
src={process.env.NEXT_PUBLIC_S3_BUCKET + article.posterImage}
|
||||||
|
fill
|
||||||
|
alt={article.title}
|
||||||
|
sizes="100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-0 w-full h-full bg-[linear-gradient(to_bottom,#00000000,#00000099)] bg-no-repeat" />
|
||||||
|
<div className="flex justify-between gap-10 absolute bottom-6 left-[75px] right-[75px]">
|
||||||
|
<p className="heading1 font-medium">{article.title}</p>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{article.tags.map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag}
|
||||||
|
className="bg-[#37393B99] rounded-[17px] px-3 py-2 btns font-medium"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=""></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,47 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { ItemActions } from '@/components/ItemActions';
|
||||||
|
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||||
import { IArticle } from '@/types/IArticle';
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { PostTag } from '@/ui/PostTag';
|
import { PostTag } from '@/ui/PostTag';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
export function ArticleCard({
|
export function ArticleCard({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
cardImage,
|
cardImage,
|
||||||
tags,
|
tags,
|
||||||
|
blocks,
|
||||||
|
drafted,
|
||||||
|
posterImage,
|
||||||
|
createdAt,
|
||||||
className,
|
className,
|
||||||
}: IArticle & { className?: string }) {
|
}: IArticle & { className?: string }) {
|
||||||
const params = useSearchParams();
|
const params = useSearchParams();
|
||||||
|
|
||||||
|
const { push } = useRouter();
|
||||||
|
|
||||||
|
const { data: auth } = useCheckAuthQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div
|
||||||
href={`/blog/${id}`}
|
onClick={(e) => push('/blog/' + id)}
|
||||||
className={`relative space-y-3${
|
className={`space-y-3${
|
||||||
className ? ' ' + className : ''
|
className ? ' ' + className : ''
|
||||||
} hover:backdrop-blur-[500px] hover:bg-[radial-gradient(ellipse_at_bottom,#7A7A7A,transparent)] bg-cover rounded-2xl relative`}
|
} hover:backdrop-blur-[500px] cursor-pointer group hover:bg-[radial-gradient(ellipse_at_bottom,#7A7A7A99,transparent)] transition-[background-size] bg-[length:100%_0%] bg-bottom hover:bg-[length:100%_100%] bg-no-repeat rounded-2xl p-3 relative h-full`}
|
||||||
>
|
>
|
||||||
<Image
|
<div className="relative">
|
||||||
src={process.env.NEXT_PUBLIC_S3_BUCKET + cardImage}
|
<Image
|
||||||
alt={title}
|
src={process.env.NEXT_PUBLIC_S3_BUCKET + cardImage}
|
||||||
fill
|
alt={title}
|
||||||
className="!relative object-cover"
|
fill
|
||||||
priority
|
className="!relative object-cover rounded-xl z-[1]"
|
||||||
/>
|
/>
|
||||||
|
{auth && (
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full z-[2] group-hover:block hidden bg-[#0F1011] bg-opacity-40 rounded-2xl" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<p className="heading1 font-medium">{title}</p>
|
<p className="heading1 font-medium">{title}</p>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
@@ -39,6 +52,18 @@ export function ArticleCard({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
<ItemActions
|
||||||
|
item={{
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
cardImage,
|
||||||
|
tags,
|
||||||
|
blocks,
|
||||||
|
drafted,
|
||||||
|
posterImage,
|
||||||
|
createdAt,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,35 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useGetArticlesQuery } from '@/queries/getArticles';
|
import { useGetArticlesQuery } from '@/queries/getArticles';
|
||||||
import { TagsFilters } from '../ProjectsPage/TagsFilters';
|
import { useGetDraftedArticlesQuery } from '@/queries/getDraftedArticles';
|
||||||
import { ArticleCard } from './ArticleCard';
|
import { ArticleCard } from './ArticleCard';
|
||||||
|
|
||||||
export function ArticlesList({ tags }: { tags: string[] }) {
|
export function ArticlesList({ tags }: { tags: string[] }) {
|
||||||
const { data: articles } = useGetArticlesQuery(tags);
|
const { data: articles } = useGetArticlesQuery(tags);
|
||||||
|
|
||||||
|
const { data: drafted } = useGetDraftedArticlesQuery(tags);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid lg:grid-cols-[1fr_4fr_1fr] items-start gap-x-[34px] relative">
|
<div className="space-y-6 col-span-4">
|
||||||
<TagsFilters type="article" tags={tags} />
|
{drafted && drafted.length > 0 ? (
|
||||||
<div className="mt-12">
|
<div className="space-y-2">
|
||||||
{articles && articles.length > 0 ? (
|
<p className="text-[#7A7A7A] text1">Черновики</p>
|
||||||
|
<div className="gap-x-3 gap-y-6 flex flex-wrap col-start-2">
|
||||||
|
{drafted?.map((article, index) => (
|
||||||
|
<ArticleCard
|
||||||
|
key={article.id}
|
||||||
|
{...article}
|
||||||
|
className={index % 5 < 4 ? 'lg:w-1/3 sm:w-1/2' : 'sm:w-1/2'}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{articles && articles.length > 0 ? (
|
||||||
|
<div className="space-y-2 pt-2">
|
||||||
|
<p className="text-[#7A7A7A] text1">Опубликованое</p>
|
||||||
<div className="gap-x-3 gap-y-6 flex flex-wrap col-start-2">
|
<div className="gap-x-3 gap-y-6 flex flex-wrap col-start-2">
|
||||||
{articles?.map((article, index) => (
|
{articles?.map((article, index) => (
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
@@ -25,10 +43,10 @@ export function ArticlesList({ tags }: { tags: string[] }) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<p className="heading1 font-medium text-center">Статьи не найдены</p>
|
) : (
|
||||||
)}
|
<p className="heading1 font-medium text-center">Статьи не найдены</p>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { PlusIcon } from '@/components/icons/PlusIcon';
|
||||||
|
import { ArticleFormModal } from '@/components/modals/ArticleFormModal';
|
||||||
|
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
||||||
|
import { OpenFormModalWrapper } from '@/hocs/OpenFormModalWrapper';
|
||||||
|
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||||
|
import { useGetDraftedArticlesQuery } from '@/queries/getDraftedArticles';
|
||||||
|
import { IArticle } from '@/types/IArticle';
|
||||||
|
import { Button } from '@/ui/Button';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { TagsFilters } from '../ProjectsPage/TagsFilters';
|
||||||
|
|
||||||
|
export function ArticlesPageActions({ tags }: { tags: string[] }) {
|
||||||
|
const { data: drafts, refetch } = useGetDraftedArticlesQuery(tags);
|
||||||
|
|
||||||
|
const { data: auth } = useCheckAuthQuery();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [hiddenDrafts, setDraftedArticles] = useState<IArticle[]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShow(!!drafts);
|
||||||
|
}, [drafts]);
|
||||||
|
|
||||||
|
function handleShowDrafted() {
|
||||||
|
if (!drafts) {
|
||||||
|
refetch();
|
||||||
|
queryClient.setQueryData(['articles', 'drafted', tags], hiddenDrafts);
|
||||||
|
setDraftedArticles([]);
|
||||||
|
} else {
|
||||||
|
setDraftedArticles(drafts);
|
||||||
|
queryClient.setQueryData(['articles', 'drafted', tags], []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sticky top-12 bottom-5 flex flex-col justify-between gap-y-4 self-stretch">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<OpenFormModalWrapper
|
||||||
|
modalName="addArticle"
|
||||||
|
modal={<ArticleFormModal action="create" />}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="px-3 py-2 outline-none btns"
|
||||||
|
rounded="xl"
|
||||||
|
icon={
|
||||||
|
<ClassNameWrapper className="w-4 h-4">
|
||||||
|
<PlusIcon />
|
||||||
|
</ClassNameWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Добавить статью
|
||||||
|
</Button>
|
||||||
|
</OpenFormModalWrapper>
|
||||||
|
{auth && (
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
className={`active:bg-white active:text-black px-3 transition-colors py-2 btns ${
|
||||||
|
show ? 'bg-white text-black' : 'bg-[#37393B99]'
|
||||||
|
}`}
|
||||||
|
rounded="xl"
|
||||||
|
onClick={handleShowDrafted}
|
||||||
|
>
|
||||||
|
Показать черновики
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<TagsFilters type="article" tags={tags} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { PlusIcon } from '@/components/icons/PlusIcon';
|
|
||||||
import { ArticleFormModal } from '@/components/modals/ArticleHeaderFormModal';
|
|
||||||
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
|
|
||||||
import { OpenFormModalWrapper } from '@/hocs/OpenFormModalWrapper';
|
|
||||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
|
||||||
import { IArticle } from '@/types/IArticle';
|
|
||||||
import { Button } from '@/ui/Button';
|
|
||||||
import { Title } from '@/ui/Title';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
export function ArticlesPageHeader() {
|
|
||||||
const auth = useCheckAuthQuery();
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="lg:space-y-14 sm:space-y-8 space-y-10 relative">
|
|
||||||
<Title className="text-center" headerLevel={2}>
|
|
||||||
В блоге собраны все публикации о работе компании:
|
|
||||||
<span className="text-[#7A7A7A]"> новости, статьи и видео</span>
|
|
||||||
</Title>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<OpenFormModalWrapper
|
|
||||||
modalName="addArticle"
|
|
||||||
className="sticky top-0"
|
|
||||||
modal={<ArticleFormModal action="create" />}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="px-3 py-2 outline-none btns"
|
|
||||||
rounded="xl"
|
|
||||||
icon={
|
|
||||||
<ClassNameWrapper className="w-4 h-4">
|
|
||||||
<PlusIcon />
|
|
||||||
</ClassNameWrapper>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Добавить статью
|
|
||||||
</Button>
|
|
||||||
</OpenFormModalWrapper>
|
|
||||||
{auth && (
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
className="bg-[#37393B99] active:bg-white active:text-black px-3 py-2 btns"
|
|
||||||
rounded="xl"
|
|
||||||
onClick={() => {
|
|
||||||
queryClient.setQueryData(['articles'], () =>
|
|
||||||
queryClient.setQueryData<IArticle[]>(
|
|
||||||
['articles'],
|
|
||||||
queryClient
|
|
||||||
.getQueryData<IArticle[]>(['articles'])
|
|
||||||
?.filter((article) => article.drafted)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Показать черновики
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ export function TagsFilters({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-2 sticky left-5 bottom-6 h-fit max-lg:hidden row-start-1 self-end">
|
<div className="space-y-2 max-lg:hidden">
|
||||||
{['Все', ...(type === 'project' ? projectsTags : postTags)].map(
|
{['Все', ...(type === 'project' ? projectsTags : postTags)].map(
|
||||||
(tag) => (
|
(tag) => (
|
||||||
<TagFilter
|
<TagFilter
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function useArticleMutation({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (json: IArticle) =>
|
mutationFn: async (json: Omit<IArticle, 'blocks'> & { blocks: string }) =>
|
||||||
action === 'create'
|
action === 'create'
|
||||||
? await api.post('articles', { json }).json<IArticle>()
|
? await api.post('articles', { json }).json<IArticle>()
|
||||||
: await api.put(`articles/${id}`, { json }).json<IArticle>(),
|
: await api.put(`articles/${id}`, { json }).json<IArticle>(),
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { api } from '@/api';
|
import { api } from '@/api';
|
||||||
import { useModalStore } from '@/stores/useModalStore';
|
import { useModalStore } from '@/stores/useModalStore';
|
||||||
import { IProject } from '@/types/IProject';
|
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
export function useDeleteMutation(
|
export function useDeleteMutation(
|
||||||
entity: 'projects' | 'companies',
|
entity: 'projects' | 'companies' | 'articles',
|
||||||
id: string
|
id: string
|
||||||
) {
|
) {
|
||||||
const { setModal } = useModalStore();
|
const { setModal } = useModalStore();
|
||||||
@@ -12,10 +11,13 @@ export function useDeleteMutation(
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { mutateAsync: remove } = useMutation({
|
const { mutateAsync: remove } = useMutation({
|
||||||
mutationFn: async () =>
|
mutationFn: async () => await api.delete(`${entity}/${id}`).json(),
|
||||||
await api.delete(`${entity}/${id}`).json<IProject>(),
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: [entity] });
|
queryClient.invalidateQueries({ queryKey: [entity] });
|
||||||
|
|
||||||
|
if (entity === 'articles')
|
||||||
|
queryClient.setQueryData(['articles', 'drafted', []], []);
|
||||||
|
|
||||||
setModal(null, '');
|
setModal(null, '');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { getQueryClient } from './queryClient';
|
import { getQueryClient } from './queryClient';
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@ export const Providers = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const [queryClient] = useState(getQueryClient);
|
const [queryClient] = useState(getQueryClient);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{children}
|
||||||
|
<ReactQueryDevtools />
|
||||||
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { api } from '@/api';
|
||||||
|
import { IArticle } from '@/types/IArticle';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
export function useGetDraftedArticlesQuery(tags: string | string[] = []) {
|
||||||
|
return useQuery({
|
||||||
|
enabled: false,
|
||||||
|
queryKey: ['articles', 'drafted', tags],
|
||||||
|
queryFn: async () =>
|
||||||
|
await api
|
||||||
|
.get(
|
||||||
|
`articles/drafted?${
|
||||||
|
Array.isArray(tags)
|
||||||
|
? tags.map((tag) => `tags=${tag}`).join('&')
|
||||||
|
: 'tags=' + tags
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
.json<IArticle[]>(),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IArticle } from '@/types/IArticle';
|
||||||
|
import { ICompany } from '@/types/ICompany';
|
||||||
|
import { IProject } from '@/types/IProject';
|
||||||
|
|
||||||
|
export function isCompany(
|
||||||
|
item: IProject | ICompany | IArticle
|
||||||
|
): item is ICompany {
|
||||||
|
return 'color' in item;
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { IArticle } from '@/types/IArticle';
|
||||||
import { ICompany } from '@/types/ICompany';
|
import { ICompany } from '@/types/ICompany';
|
||||||
import { IProject } from '@/types/IProject';
|
import { IProject } from '@/types/IProject';
|
||||||
|
|
||||||
export function isProject(item: IProject | ICompany): item is IProject {
|
export function isProject(
|
||||||
|
item: IProject | ICompany | IArticle
|
||||||
|
): item is IProject {
|
||||||
return 'image' in item;
|
return 'image' in item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,6 +335,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.62.7.tgz#c7f6d0131c08cd2f60e73ec6e7b70e2e9e335def"
|
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.62.7.tgz#c7f6d0131c08cd2f60e73ec6e7b70e2e9e335def"
|
||||||
integrity sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==
|
integrity sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==
|
||||||
|
|
||||||
|
"@tanstack/query-devtools@5.64.2":
|
||||||
|
version "5.64.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.64.2.tgz#3d8f8abb17815a7302482b67713cb933c79ed6ba"
|
||||||
|
integrity sha512-3DautR5UpVZdk/qNIhioZVF7g8fdQZ1U98sBEEk4Tzz3tihSBNMPgwlP40nzgbPEDBIrn/j/oyyvNBVSo083Vw==
|
||||||
|
|
||||||
|
"@tanstack/react-query-devtools@^5.64.2":
|
||||||
|
version "5.64.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.64.2.tgz#ece61dfad8032305aefd3f0eb044ccd8304ffa1b"
|
||||||
|
integrity sha512-+ZjJVnPzc8BUV/Eklu2k9T/IAyAyvwoCHqOaOrk2sbU33LFhM52BpX4eyENXn0bx5LwV3DJZgEQlIzucoemfGQ==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-devtools" "5.64.2"
|
||||||
|
|
||||||
"@tanstack/react-query@^5.62.7":
|
"@tanstack/react-query@^5.62.7":
|
||||||
version "5.62.7"
|
version "5.62.7"
|
||||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.62.7.tgz#8f253439a38ad6ce820bc6d42d89ca2556574d1a"
|
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.62.7.tgz#8f253439a38ad6ce820bc6d42d89ca2556574d1a"
|
||||||
|
|||||||
Reference in New Issue
Block a user