load more completed
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
'use server';
|
||||
import { getClient } from '@/lib/apolloClientForSC';
|
||||
import {
|
||||
GetArticlesDocument,
|
||||
GetArticlesQuery,
|
||||
} from '@/queries/articles/getArticles';
|
||||
|
||||
export async function getArticlesWithPaginationAction(
|
||||
offset: number = 0,
|
||||
limit: number = 5,
|
||||
tags: string[] = [],
|
||||
) {
|
||||
return (
|
||||
await getClient().query<GetArticlesQuery>({
|
||||
query: GetArticlesDocument,
|
||||
variables: { offset, limit, tags },
|
||||
})
|
||||
).data.articles;
|
||||
}
|
||||
@@ -1,45 +1,22 @@
|
||||
import { getArticlesWithPaginationAction } from '@/actions/articles/getArticlesWithPaginationAction';
|
||||
import { ArticlesList } from '@/components/pages/BlogPage/ArticlesList';
|
||||
import { ArticlesPageHeader } from '@/components/pages/BlogPage/ArticlesPageHeader';
|
||||
import { ArticlesResult } from '@/generated/graphql';
|
||||
import { getClient } from '@/lib/apolloClientForSC';
|
||||
import { GetArticlesDocument } from '@/queries/articles/getArticles';
|
||||
import { GetArticlesCountDocument } from '@/queries/articles/getArticlesCount';
|
||||
|
||||
export default async function BlogPage({
|
||||
searchParams: { tags = [], limit = 5 },
|
||||
searchParams: { tags = [] },
|
||||
}: {
|
||||
searchParams: {
|
||||
tags?: string[];
|
||||
limit: number;
|
||||
};
|
||||
}) {
|
||||
const client = getClient();
|
||||
const data = await getArticlesWithPaginationAction(0, 5, tags);
|
||||
|
||||
const {
|
||||
data: { articles },
|
||||
} = await client.query<{
|
||||
articles: ArticlesResult;
|
||||
}>({
|
||||
query: GetArticlesDocument,
|
||||
variables: {
|
||||
tags,
|
||||
limit: +limit,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: { articlesCount },
|
||||
} = await client.query<{ articlesCount: number }>({
|
||||
query: GetArticlesCountDocument,
|
||||
variables: {
|
||||
tags,
|
||||
},
|
||||
});
|
||||
if (data.__typename !== 'Articles') return <div>error</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ArticlesPageHeader />
|
||||
<ArticlesList articlesData={articles} articlesCount={articlesCount} />
|
||||
<ArticlesList articles={data.articles} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
@layer base {
|
||||
.no-tailwindcss-base {
|
||||
@apply w-full;
|
||||
word-break: normal;
|
||||
h1 {
|
||||
@apply text-[32px] font-medium leading-[35.2px];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cities } from '@/consts/cities';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
export function CitySelector() {
|
||||
const { push } = useRouter();
|
||||
@@ -21,6 +21,12 @@ export function CitySelector() {
|
||||
|
||||
useEffect(() => setValue(params.get('city') as string), [params]);
|
||||
|
||||
function handleChangeSelect(e: ChangeEvent<HTMLSelectElement>) {
|
||||
if (!e.target.value) params.delete('city');
|
||||
else params.set('city', e.target.value);
|
||||
push(pathname + '?' + params.toString());
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center border border-[#3D425C] rounded-full 2xl:col-start-11 2xl:col-span-2 lg:col-start-10 nice-cock-2 lg:col-span-3 md:col-start-9 md:col-span-4 sm:col-start-8 sm:col-span-5 self-start w-full">
|
||||
<p
|
||||
@@ -40,11 +46,7 @@ export function CitySelector() {
|
||||
: `url("data:image/svg+xml;utf8,<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M12.0001 17.707L19.7072 9.99992L18.293 8.58571L12.0001 14.8786L5.70718 8.58571L4.29297 9.99992L12.0001 17.707Z' fill='white'/></svg>")`,
|
||||
}}
|
||||
className="bg-[#14161F] flex-1 appearance-none [-webkit-appearance:none] [-moz-appearance:none] rounded-full p-[10px] outline-none bg-no-repeat bg-[right_10px_center]"
|
||||
onChange={e => {
|
||||
if (!e.target.value) params.delete('city');
|
||||
else params.set('city', e.target.value);
|
||||
push(pathname + '?' + params.toString());
|
||||
}}
|
||||
onChange={handleChangeSelect}
|
||||
>
|
||||
{!!ref.current && (
|
||||
<>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function FilterItem({
|
||||
|
||||
const params = new URLSearchParams(useSearchParams());
|
||||
|
||||
function clickHandler() {
|
||||
function handleClick() {
|
||||
if (isAll) {
|
||||
params.delete(type);
|
||||
} else {
|
||||
@@ -44,7 +44,7 @@ export function FilterItem({
|
||||
? 'bg-[#798FFF] border-[#798FFF]'
|
||||
: 'border-[#3D425C] nice-cock-2'
|
||||
}`}
|
||||
onClick={clickHandler}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{value}
|
||||
{!!count && (
|
||||
@@ -15,9 +15,7 @@ export function ProductsList({ inBurger = false }: { inBurger?: boolean }) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
onClick={() => {
|
||||
setModal(<MenuModal />, 'menu');
|
||||
}}
|
||||
onClick={() => setModal(<MenuModal />, 'menu')}
|
||||
className={
|
||||
`btn-text font-medium flex gap-x-2 items-center border-[#3D425C] nice-cock-2 hover:text-white group outline-none ${name === 'menu' ? 'text-white' : ''}` +
|
||||
(name === 'products' ? ' relative z-[101]' : '') +
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getCountryCallingCode,
|
||||
} from 'libphonenumber-js';
|
||||
import Image from 'next/image';
|
||||
import { useRef, useState } from 'react';
|
||||
import { SyntheticEvent, useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { ChevronDownIcon } from '../icons/ChevronDownIcon';
|
||||
import { ChevronUpIcon } from '../icons/ChevronUpIcon';
|
||||
@@ -24,14 +24,21 @@ export function SelectPhoneCode({
|
||||
|
||||
useOnClickOutside(ref, () => setOpen(false));
|
||||
|
||||
function handleExpand(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
}
|
||||
|
||||
function pickPhoneCode(phoneCode: string, country: CountryCode) {
|
||||
onClick([phoneCode, country as CountryCode]);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative flex flex-col sm:w-1/3 max-w-[350px]">
|
||||
<button
|
||||
className="relative flex items-center justify-between gap-x-1"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
}}
|
||||
onClick={handleExpand}
|
||||
>
|
||||
<Image
|
||||
width={16}
|
||||
@@ -58,10 +65,7 @@ export function SelectPhoneCode({
|
||||
<div
|
||||
key={country}
|
||||
className="flex items-center gap-x-1 hover:bg-[#3D425C] px-1"
|
||||
onClick={() => {
|
||||
onClick([phoneCode, country as CountryCode]);
|
||||
setOpen(false);
|
||||
}}
|
||||
onClick={() => pickPhoneCode(phoneCode, country as CountryCode)}
|
||||
>
|
||||
<Image
|
||||
src={countries.find(c => c.iso === country)?.flag || ''}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { UseFieldArrayRemove, UseFormSetValue } from 'react-hook-form';
|
||||
import { CloseIcon } from '../icons/CloseIcon';
|
||||
import { MediaUploader } from '../MediaUploader';
|
||||
@@ -19,6 +20,11 @@ export function ArticleSliderImageInput({
|
||||
remove,
|
||||
setValue,
|
||||
}: IArticleSliderImageInputProps) {
|
||||
function handleClickClose(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
remove(imgIndex);
|
||||
}
|
||||
|
||||
return (
|
||||
<Reorder.Item value={item} className="flex items-center" drag>
|
||||
<MediaUploader
|
||||
@@ -28,13 +34,7 @@ export function ArticleSliderImageInput({
|
||||
item={item}
|
||||
label="Выберите изображение"
|
||||
/>
|
||||
<button
|
||||
className="self-start z-[1]"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
remove(imgIndex);
|
||||
}}
|
||||
>
|
||||
<button className="self-start z-[1]" onClick={handleClickClose}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</Reorder.Item>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ISlider } from '@/types/IArticle';
|
||||
import { reorderFields } from '@/utils/reorderFields';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import {
|
||||
Control,
|
||||
useFieldArray,
|
||||
@@ -32,26 +33,26 @@ export function ArticleSliderInput({
|
||||
name: `blocks.${index}.images`,
|
||||
});
|
||||
|
||||
function handleAddSlide(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
append({ img: '' });
|
||||
}
|
||||
|
||||
function handleRemoveSlider(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
removeSlider(index);
|
||||
}
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
value={item}
|
||||
className="border border-[#3D425C] rounded-3xl p-4 col-span-full space-y-4"
|
||||
>
|
||||
<div className="flex gap-x-4 justify-center">
|
||||
<button
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
append({ img: '' });
|
||||
}}
|
||||
>
|
||||
<button onClick={handleAddSlide}>
|
||||
<PlusIcon />
|
||||
</button>
|
||||
<button
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
removeSlider(index);
|
||||
}}
|
||||
>
|
||||
<button onClick={handleRemoveSlider}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ export function ArticleContentEditorModal({
|
||||
}}
|
||||
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',
|
||||
'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 }) =>
|
||||
|
||||
@@ -30,6 +30,23 @@ export function ArticleFormModal({ defaultValues, set }: IArticleFormProps) {
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
function onSubmit({
|
||||
cardImage,
|
||||
createdAt,
|
||||
description,
|
||||
tags,
|
||||
title,
|
||||
}: Omit<IArticleFormInput, 'blocks'>) {
|
||||
setModal(null, '');
|
||||
if (set) {
|
||||
set('title', title);
|
||||
set('description', description);
|
||||
set('tags', tags);
|
||||
set('cardImage', cardImage);
|
||||
set('createdAt', createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-black bg-white p-4 rounded-lg relative top-10 space-y-3">
|
||||
<div className="flex justify-between items-center border-b border-[#ccc] pb-4 gap-4">
|
||||
@@ -41,21 +58,7 @@ export function ArticleFormModal({ defaultValues, set }: IArticleFormProps) {
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit(
|
||||
({ cardImage, description, tags, title, createdAt }) => {
|
||||
setModal(null, '');
|
||||
if (set) {
|
||||
set('title', title);
|
||||
set('description', description);
|
||||
set('tags', tags);
|
||||
set('cardImage', cardImage);
|
||||
set('createdAt', createdAt);
|
||||
}
|
||||
},
|
||||
)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<label htmlFor="blog_title" className="flex flex-col">
|
||||
Название статьи
|
||||
<input
|
||||
|
||||
@@ -7,15 +7,6 @@ import { CloseIcon } from '../icons/CloseIcon';
|
||||
export function DeleteArticleModal({ id }: { id: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
// const [deleteArticle] = useDeleteArticleMutation({
|
||||
// variables: { id },
|
||||
// refetchQueries: ['GetArticleById', 'GetArticlesCount'],
|
||||
// onCompleted: () => {
|
||||
// setModal(null, '');
|
||||
// window.location.href = '/blog';
|
||||
// },
|
||||
// });
|
||||
|
||||
const client = useApolloClient();
|
||||
|
||||
async function handleDeleteArticle() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { FilterItem } from '@/components/TagFilterItem';
|
||||
import { FilterItem } from '@/components/FilterItem';
|
||||
import { PostTags } from '@/consts/PostTags';
|
||||
import { useGetArticlesCountQuery } from '@/queries/articles/getArticlesCount';
|
||||
import { Vertical } from '@/ui/Vertical';
|
||||
@@ -8,6 +8,7 @@ import { useSearchParams } from 'next/navigation';
|
||||
|
||||
export function ArticlesFilters() {
|
||||
const params = useSearchParams();
|
||||
|
||||
const chosedTags = params.getAll('tags');
|
||||
|
||||
const { data } = useGetArticlesCountQuery({ variables: { tags: [] } });
|
||||
|
||||
@@ -1,64 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { ArticlesResult } from '@/generated/graphql';
|
||||
import { getArticlesWithPaginationAction } from '@/actions/articles/getArticlesWithPaginationAction';
|
||||
import { Article } from '@/generated/graphql';
|
||||
import { useGetArticlesCountQuery } from '@/queries/articles/getArticlesCount';
|
||||
import { Block } from '@/types/IArticle';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ArticleCard } from './ArticleCard';
|
||||
|
||||
export function ArticlesList({
|
||||
articlesData,
|
||||
articlesCount,
|
||||
}: {
|
||||
articlesData: ArticlesResult;
|
||||
articlesCount: number;
|
||||
}) {
|
||||
const { push } = useRouter();
|
||||
export function ArticlesList({ articles }: { articles: Article[] }) {
|
||||
const params = useSearchParams();
|
||||
|
||||
const pathname = usePathname();
|
||||
const [nextOffset, setNextOffset] = useState(5);
|
||||
const [currentArticles, setCurrentArticles] = useState(articles);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const params = new URLSearchParams(useSearchParams());
|
||||
const [limit, setLimit] = useState(5);
|
||||
const { data } = useGetArticlesCountQuery({
|
||||
variables: { tags: params.getAll('tags') },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
params.set('limit', limit.toString());
|
||||
push(pathname + '?' + params.toString());
|
||||
}, [limit, params, pathname, push]);
|
||||
setCurrentArticles(articles);
|
||||
}, [params, articles]);
|
||||
|
||||
function handleShowMore() {
|
||||
if (articlesData.__typename === 'Articles' && limit < articlesCount)
|
||||
setLimit(prev => prev + 5);
|
||||
}
|
||||
const handleLoadMore = useCallback(async () => {
|
||||
const data = await getArticlesWithPaginationAction(
|
||||
nextOffset,
|
||||
5,
|
||||
params.getAll('tags'),
|
||||
);
|
||||
if (data.__typename === 'Articles') {
|
||||
setCurrentArticles(prev => [...prev, ...data.articles]);
|
||||
setNextOffset(prev => prev + 5);
|
||||
}
|
||||
}, [nextOffset, params]);
|
||||
|
||||
return (
|
||||
<div className="lg:pb-6">
|
||||
{articlesData.__typename === 'Articles' &&
|
||||
articlesData.articles.length === 0 ? (
|
||||
{currentArticles.length === 0 ? (
|
||||
<p className="text-center mt-10 h3 font-medium">Постов не найдено</p>
|
||||
) : (
|
||||
<div className="lg:mb-6 sm:mb-5 mb-4">
|
||||
{articlesData?.__typename === 'Articles' &&
|
||||
articlesData?.articles?.map(article => (
|
||||
<ArticleCard
|
||||
key={article.id}
|
||||
{...article}
|
||||
blocks={
|
||||
article.blocks.map(block => ({
|
||||
...block,
|
||||
type: block.__typename,
|
||||
})) as Block[]
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<div className="lg:pl-[calc(25vw-40px)] mt-10">
|
||||
<button
|
||||
onClick={handleShowMore}
|
||||
className="lg:opacity-80 lg:hover:opacity-100 btn-text font-semibold border border-[#3D425C] rounded-[32px] lg:py-[14.5px] sm:py-6 py-4 w-full transition-all nice-cock-2"
|
||||
>
|
||||
Показать еще
|
||||
</button>
|
||||
</div>
|
||||
{currentArticles?.map(article => (
|
||||
<ArticleCard
|
||||
key={article.id}
|
||||
{...article}
|
||||
blocks={
|
||||
article.blocks.map(block => ({
|
||||
...block,
|
||||
type: block.__typename,
|
||||
})) as Block[]
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{data?.articlesCount &&
|
||||
currentArticles.length < data?.articlesCount && (
|
||||
<div className="lg:pl-[calc(25vw-40px)] mt-10">
|
||||
<button
|
||||
onClick={handleLoadMore}
|
||||
className="lg:opacity-80 lg:hover:opacity-100 btn-text font-semibold border border-[#3D425C] rounded-[32px] lg:py-[14.5px] sm:py-6 py-4 w-full transition-all nice-cock-2"
|
||||
>
|
||||
Показать еще
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { awards } from '@/consts/awards';
|
||||
import { Title } from '@/ui/Title';
|
||||
import Image from 'next/image';
|
||||
|
||||
@@ -5,23 +6,7 @@ export function Awards() {
|
||||
return (
|
||||
<div className="grid lg:grid-cols-2 sm:max-lg:grid-rows-[104px_1fr_1fr_1fr] lg:py-40 sm:py-20 py-12">
|
||||
<Title className="sm:max-lg:mb-16 max-sm:mb-12">Награды</Title>
|
||||
{[
|
||||
{
|
||||
title: 'Победители BuildUP 2023',
|
||||
description: 'в номинации IT',
|
||||
image: '/img/pages/home/awards/BuildUP.png',
|
||||
},
|
||||
{
|
||||
title: '1 место',
|
||||
description: 'WOW AWARDS 2024 ',
|
||||
image: '/img/pages/home/awards/wow_awards.png',
|
||||
},
|
||||
{
|
||||
title: 'Лучшее на Dprofile',
|
||||
description: 'а еще за UI награда',
|
||||
image: '/img/pages/home/awards/dpui.png',
|
||||
},
|
||||
].map((awards, index) => (
|
||||
{awards.map((awards, index) => (
|
||||
<AwardsItem key={index} {...awards} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,13 @@ export function Projects({ projects }: { projects: ProjectsResult }) {
|
||||
setModal(null, '');
|
||||
}
|
||||
|
||||
function handleAddProject() {
|
||||
setModal(
|
||||
<ProjectFormModal action={'create'} onSubmit={onSubmit} />,
|
||||
'addProject',
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="border-y border-[#3D425C] py-6 grid lg:grid-cols-4 sm:gap-y-8 gap-y-6">
|
||||
@@ -52,16 +59,7 @@ export function Projects({ projects }: { projects: ProjectsResult }) {
|
||||
{authData?.checkAuth.__typename === 'CheckAuthResponse' &&
|
||||
authData?.checkAuth.isAuth && (
|
||||
<div className="m-auto">
|
||||
<Button
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ProjectFormModal action={'create'} onSubmit={onSubmit} />,
|
||||
'addProject',
|
||||
)
|
||||
}
|
||||
>
|
||||
Добавить проект
|
||||
</Button>
|
||||
<Button onClick={handleAddProject}>Добавить проект</Button>
|
||||
</div>
|
||||
)}
|
||||
<Link
|
||||
|
||||
@@ -10,6 +10,16 @@ export function Showreel() {
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
function handleFullScreenClick() {
|
||||
setModal(
|
||||
<VideoModal
|
||||
currentTime={videoRef.current?.currentTime ?? 0}
|
||||
link={'/videos/pages/home/showreel.mp4'}
|
||||
/>,
|
||||
'video',
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lg:mb-[200px] sm:mb-[120px] mb-20 w-full relative lg:aspect-[1551/616] flex justify-center items-center group">
|
||||
<video
|
||||
@@ -23,15 +33,7 @@ export function Showreel() {
|
||||
/>
|
||||
<button
|
||||
className="absolute z-[8] p-8 rounded-full border group-hover:block hidden bg-[#14161F33]"
|
||||
onClick={() => {
|
||||
setModal(
|
||||
<VideoModal
|
||||
currentTime={videoRef.current?.currentTime ?? 0}
|
||||
link={'/videos/pages/home/showreel.mp4'}
|
||||
/>,
|
||||
'video',
|
||||
);
|
||||
}}
|
||||
onClick={handleFullScreenClick}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { FilterItem } from '@/components/TagFilterItem';
|
||||
import { FilterItem } from '@/components/FilterItem';
|
||||
import { Devices } from '@/consts/ProjectTags';
|
||||
import { useGetProjectsCountQuery } from '@/queries/projects/getProjectsCount';
|
||||
import { Vertical } from '@/ui/Vertical';
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const PostYears = [
|
||||
'2024',
|
||||
'2023',
|
||||
'2022',
|
||||
'2021',
|
||||
'2020',
|
||||
'2019',
|
||||
'2018',
|
||||
];
|
||||
@@ -1 +0,0 @@
|
||||
export const Products = [];
|
||||
@@ -1,15 +0,0 @@
|
||||
export const ProjectYears = [
|
||||
'2024',
|
||||
'2023',
|
||||
'2022',
|
||||
'2021',
|
||||
'2020',
|
||||
'2019',
|
||||
'2018',
|
||||
'2017',
|
||||
'2016',
|
||||
'2015',
|
||||
'2014',
|
||||
'2013',
|
||||
'2012',
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IAward } from '@/types/IAward';
|
||||
|
||||
export const awards: IAward[] = [
|
||||
{
|
||||
title: 'Победители BuildUP 2023',
|
||||
description: 'в номинации IT',
|
||||
image: '/img/pages/home/awards/BuildUP.png',
|
||||
},
|
||||
{
|
||||
title: '1 место',
|
||||
description: 'WOW AWARDS 2024 ',
|
||||
image: '/img/pages/home/awards/wow_awards.png',
|
||||
},
|
||||
{
|
||||
title: 'Лучшее на Dprofile',
|
||||
description: 'а еще за UI награда',
|
||||
image: '/img/pages/home/awards/dpui.png',
|
||||
},
|
||||
];
|
||||
@@ -1,3 +0,0 @@
|
||||
import { PhoneCode } from '@/types/PhoneCode';
|
||||
|
||||
export const phoneCodes: PhoneCode[] = ['+7', '+375', '+380', '+44'];
|
||||
@@ -191,6 +191,7 @@ export type QueryArticleArgs = {
|
||||
|
||||
export type QueryArticlesArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
offset?: InputMaybe<Scalars['Int']['input']>;
|
||||
tags?: Array<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const defaultOptions = {} as const;
|
||||
export type GetArticlesQueryVariables = Types.Exact<{
|
||||
tags: Array<Types.Scalars['String']['input']> | Types.Scalars['String']['input'];
|
||||
limit?: Types.InputMaybe<Types.Scalars['Int']['input']>;
|
||||
offset?: Types.InputMaybe<Types.Scalars['Int']['input']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -13,8 +14,8 @@ export type GetArticlesQuery = { __typename?: 'Query', articles: { __typename?:
|
||||
|
||||
|
||||
export const GetArticlesDocument = gql`
|
||||
query GetArticles($tags: [String!]!, $limit: Int) {
|
||||
articles(tags: $tags, limit: $limit) {
|
||||
query GetArticles($tags: [String!]!, $limit: Int, $offset: Int) {
|
||||
articles(tags: $tags, limit: $limit, offset: $offset) {
|
||||
... on Error {
|
||||
message
|
||||
}
|
||||
@@ -58,6 +59,7 @@ export const GetArticlesDocument = gql`
|
||||
* variables: {
|
||||
* tags: // value for 'tags'
|
||||
* limit: // value for 'limit'
|
||||
* offset: // value for 'offset'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
query GetArticles($tags: [String!]!, $limit: Int) {
|
||||
articles(tags: $tags, limit: $limit) {
|
||||
query GetArticles($tags: [String!]!, $limit: Int, $offset: Int) {
|
||||
articles(tags: $tags, limit: $limit, offset: $offset) {
|
||||
... on Error {
|
||||
message
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface IAward {
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export type PhoneCode = '+7' | '+375' | '+380' | '+44';
|
||||
@@ -1,3 +0,0 @@
|
||||
export function Select({ text }: { text: string }) {
|
||||
return <div>Select</div>;
|
||||
}
|
||||
+24
-32
@@ -38,6 +38,28 @@ export const SliderControls = forwardRef<
|
||||
right: () => nextBtnRef.current?.click(),
|
||||
}));
|
||||
|
||||
function handleLeftClick() {
|
||||
if (slide === 0) {
|
||||
rectRef.current!.classList.remove('transition-all', 'duration-300');
|
||||
rectRef.current!.setAttribute('stroke-dasharray', `${length} 0`);
|
||||
delay(() => {
|
||||
rectRef.current!.classList.add('transition-all', 'duration-300');
|
||||
}, 1);
|
||||
}
|
||||
onLeftClick();
|
||||
}
|
||||
|
||||
function handleRightClick() {
|
||||
if (slide + 1 === slidesCount) {
|
||||
rectRef.current!.classList.remove('transition-all', 'duration-300');
|
||||
rectRef.current!.setAttribute('stroke-dasharray', `0 ${length}`);
|
||||
delay(() => {
|
||||
rectRef.current!.classList.add('transition-all', 'duration-300');
|
||||
}, 1);
|
||||
}
|
||||
onRightClick();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex items-center gap-2 ' + className}>
|
||||
<div className="relative flex justify-center max-sm:order-2">
|
||||
@@ -77,44 +99,14 @@ export const SliderControls = forwardRef<
|
||||
</div>
|
||||
<button
|
||||
ref={prevBtnRef}
|
||||
onClick={() => {
|
||||
if (slide === 0) {
|
||||
rectRef.current!.classList.remove(
|
||||
'transition-all',
|
||||
'duration-300',
|
||||
);
|
||||
rectRef.current!.setAttribute('stroke-dasharray', `${length} 0`);
|
||||
delay(() => {
|
||||
rectRef.current!.classList.add(
|
||||
'transition-all',
|
||||
'duration-300',
|
||||
);
|
||||
}, 1);
|
||||
}
|
||||
onLeftClick();
|
||||
}}
|
||||
onClick={handleLeftClick}
|
||||
className="rounded-full sm:p-5 p-4 border border-[#3D425C] bg-[#14161F] nice-cock-2"
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (slide + 1 === slidesCount) {
|
||||
rectRef.current!.classList.remove(
|
||||
'transition-all',
|
||||
'duration-300',
|
||||
);
|
||||
rectRef.current!.setAttribute('stroke-dasharray', `0 ${length}`);
|
||||
delay(() => {
|
||||
rectRef.current!.classList.add(
|
||||
'transition-all',
|
||||
'duration-300',
|
||||
);
|
||||
}, 1);
|
||||
}
|
||||
onRightClick();
|
||||
}}
|
||||
ref={nextBtnRef}
|
||||
onClick={handleRightClick}
|
||||
className="rounded-full sm:p-5 p-4 border border-[#3D425C] bg-[#14161F] max-sm:order-2 nice-cock-2"
|
||||
>
|
||||
<ArrowRightIcon />
|
||||
|
||||
@@ -98,9 +98,7 @@ export function SliderWithScaling<T extends { title: string; id: number }>({
|
||||
<div {...handlers} className="h-full">
|
||||
<div
|
||||
className={`flex items-${alignItems} gap-x-4 -mr-6 select-none h-full`}
|
||||
onTransitionEnd={() => {
|
||||
setTransiting(false);
|
||||
}}
|
||||
onTransitionEnd={() => setTransiting(false)}
|
||||
style={{
|
||||
minHeight: minHeightScaled,
|
||||
transform: `translateX(${sliderOffset}px)`,
|
||||
|
||||
Reference in New Issue
Block a user