diff --git a/next.config.ts b/next.config.ts index 7a05897b..bd313d5a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,6 +6,8 @@ const cockpitHost = cockpitUrl ? new URL(cockpitUrl).hostname : '' const nextConfig: NextConfig = { images: { formats: ['image/avif', 'image/webp'], + qualities: [80, 90], + minimumCacheTTL: 43200, remotePatterns: [ { protocol: 'https', diff --git a/src/actions/forms.ts b/src/actions/forms.ts index 31d70be0..176ab7c1 100644 --- a/src/actions/forms.ts +++ b/src/actions/forms.ts @@ -102,6 +102,7 @@ export async function submitReview( const date = new Date().toISOString().slice(0, 10) const safeName = name.replace(/\s+/g, '_').replace(/[^a-zA-Zа-яёА-ЯЁ0-9_]/g, '') + const targetFolder = '6a00a163c6c8763d26aad9a3' const renamedFiles = photoFiles.map((file, index) => { const ext = file.name.includes('.') ? file.name.split('.').pop() : 'jpg' @@ -111,7 +112,8 @@ export async function submitReview( return new File([file], newName, { type: file.type }) }) - const uploadedAssets = renamedFiles.length > 0 ? await cockpit.uploadAssets(renamedFiles) : [] + const uploadedAssets = + renamedFiles.length > 0 ? await cockpit.uploadAssets(renamedFiles, targetFolder) : [] const result = await cockpit.createItem('reviews', { name, @@ -211,6 +213,7 @@ export async function submitApplication( const date = new Date().toISOString().slice(0, 10) const safeName = name.replace(/\s+/g, '_').replace(/[^a-zA-Zа-яёА-ЯЁ0-9_]/g, '') + const targetFolder = '6a03738dc6c876f8f315ef7f' const renamedFiles = photoFiles.map((file, index) => { const ext = file.name.includes('.') ? file.name.split('.').pop() : 'jpg' @@ -219,7 +222,8 @@ export async function submitApplication( return new File([file], newName, { type: file.type }) }) - const uploadedAssets = renamedFiles.length > 0 ? await cockpit.uploadAssets(renamedFiles) : [] + const uploadedAssets = + renamedFiles.length > 0 ? await cockpit.uploadAssets(renamedFiles, targetFolder) : [] const result = await cockpit.createItem('applications', { name, diff --git a/src/app/categories/[categories-detail]/page.tsx b/src/app/categories/[categories-detail]/page.tsx index 5ba5e5df..490dac53 100644 --- a/src/app/categories/[categories-detail]/page.tsx +++ b/src/app/categories/[categories-detail]/page.tsx @@ -38,7 +38,12 @@ export async function generateMetadata({ params }: PageProps): Promise ? category.description.replace(/<[^>]*>/g, '').slice(0, 160) : '', images: category.image - ? [{ url: getImageUrl(category.image._id, 1200, 630), alt: category.title }] + ? [ + { + url: getImageUrl(category.image._id, 1200, 630, { mime: 'jpeg' }), + alt: category.title, + }, + ] : [], }, alternates: { @@ -78,7 +83,7 @@ export default async function Page({ params }: PageProps): Promise const slidesList: SlideItem[] = category.slider?.map((image) => ({ id: image._id, - image: getImageUrl(image._id, 800, 800), + image: getImageUrl(image._id, 800, 500), alt: image.title || category.title, })) diff --git a/src/app/categories/page.tsx b/src/app/categories/page.tsx index cf1568e3..e70d188a 100644 --- a/src/app/categories/page.tsx +++ b/src/app/categories/page.tsx @@ -53,7 +53,7 @@ export default async function Page({ title: category.title, description: category.description, href: `/categories/${category.slug || category._id}`, - image: getImageUrl(category.image._id, 400, 400), + image: getImageUrl(category.image._id, 600, 500), alt: category.image.title || category.title, })) diff --git a/src/app/in-stock/[in-stock-detail]/page.tsx b/src/app/in-stock/[in-stock-detail]/page.tsx index 386e4544..a2f4e7bf 100644 --- a/src/app/in-stock/[in-stock-detail]/page.tsx +++ b/src/app/in-stock/[in-stock-detail]/page.tsx @@ -33,7 +33,9 @@ export async function generateMetadata({ params }: PageProps): Promise openGraph: { title: work.title, description, - images: work.image ? [{ url: getImageUrl(work.image._id, 1200, 630), alt: work.title }] : [], + images: work.image + ? [{ url: getImageUrl(work.image._id, 1200, 630, { mime: 'jpeg' }), alt: work.title }] + : [], }, alternates: { canonical: `${process.env.SITE_URL || process.env.NEXT_PUBLIC_SITE_URL}/in-stock/${work.slug || work._id}`, @@ -72,7 +74,7 @@ export default async function Page({ params }: PageProps): Promise const slidesList: SlideItem[] = work.slider?.map((image) => ({ id: image._id, - image: getImageUrl(image._id, 800, 800), + image: getImageUrl(image._id, 800, 500), alt: image.title || work.title, })) || [] @@ -87,7 +89,9 @@ export default async function Page({ params }: PageProps): Promise description: work.description ? work.description.replace(/<[^>]*>/g, '').slice(0, 160) : work.title, - image: work.image ? getImageUrl(work.image._id, 1200, 630) : undefined, + image: work.image + ? getImageUrl(work.image._id, 1200, 630, { mode: 'thumbnail', mime: 'jpeg' }) + : undefined, brand: { '@type': 'Organization', name: 'Иконописная Артель', diff --git a/src/app/in-stock/page.tsx b/src/app/in-stock/page.tsx index 0f4e2089..f1d0c8a5 100644 --- a/src/app/in-stock/page.tsx +++ b/src/app/in-stock/page.tsx @@ -58,7 +58,7 @@ export default async function Page({ title: work.title, description: work.description, href: `/in-stock/${work.slug || work._id}`, - image: getImageUrl(work.image._id, 400, 400), + image: getImageUrl(work.image._id, 600, 500), alt: work.image.title || work.title, })) diff --git a/src/app/news/[news-detail]/page.tsx b/src/app/news/[news-detail]/page.tsx index 6f9774f8..4abb9b8e 100644 --- a/src/app/news/[news-detail]/page.tsx +++ b/src/app/news/[news-detail]/page.tsx @@ -32,7 +32,9 @@ export async function generateMetadata({ params }: PageParams): Promise const slidesList: SlideItem[] = news.slider?.map((image) => ({ id: image._id, - image: getImageUrl(image._id, 800, 800), + image: getImageUrl(image._id, 800, 500), alt: image.title || news.title, })) || [] @@ -81,7 +83,7 @@ export default async function Page({ params }: PageParams): Promise '@type': 'Article', headline: news.title, description: (news.content || news.description || '').replace(/<[^>]*>/g, '').slice(0, 160), - image: news.image ? getImageUrl(news.image._id, 1200, 630) : undefined, + image: news.image ? getImageUrl(news.image._id, 1200, 630, { mime: 'jpeg' }) : undefined, datePublished: news._created ? new Date(news._created * 1000).toISOString() : undefined, dateModified: news._modified ? new Date(news._modified * 1000).toISOString() : undefined, author: { diff --git a/src/app/news/page.tsx b/src/app/news/page.tsx index abcdcb3d..7f453771 100644 --- a/src/app/news/page.tsx +++ b/src/app/news/page.tsx @@ -55,7 +55,7 @@ export default async function Page({ title: news.title, description: news.description, href: `/news/${news.slug || news._id}`, - image: getImageUrl(news.image._id, 400, 400), + image: getImageUrl(news.image._id, 600, 500), alt: news.image.title || news.title, })) diff --git a/src/app/reviews/page.tsx b/src/app/reviews/page.tsx index d3a9bc90..42b9736e 100644 --- a/src/app/reviews/page.tsx +++ b/src/app/reviews/page.tsx @@ -58,8 +58,8 @@ export default async function Page({ review.photos.forEach((img) => { if (img._id) { photos.push({ - thumb: getImageUrl(img._id, 400, 300, 'thumbnail'), - full: getImageUrl(img._id, 1920, 1080, 'bestFit'), + thumb: getImageUrl(img._id, 400, 300), + full: getImageUrl(img._id, 1920, 1080, { mode: 'bestFit' }), }) } }) diff --git a/src/app/works/[works-detail]/page.tsx b/src/app/works/[works-detail]/page.tsx index 9828aa27..7dc93978 100644 --- a/src/app/works/[works-detail]/page.tsx +++ b/src/app/works/[works-detail]/page.tsx @@ -33,7 +33,9 @@ export async function generateMetadata({ params }: PageProps): Promise openGraph: { title: work.title, description, - images: work.image ? [{ url: getImageUrl(work.image._id, 1200, 630), alt: work.title }] : [], + images: work.image + ? [{ url: getImageUrl(work.image._id, 1200, 630, { mime: 'jpeg' }), alt: work.title }] + : [], }, alternates: { canonical: `${process.env.SITE_URL || process.env.NEXT_PUBLIC_SITE_URL}/works/${work.slug || work._id}`, @@ -72,7 +74,7 @@ export default async function Page({ params }: PageProps): Promise const slidesList: SlideItem[] = work.slider?.map((image) => ({ id: image._id, - image: getImageUrl(image._id, 800, 800), + image: getImageUrl(image._id, 800, 500), alt: image.title || work.title, })) || [] @@ -87,7 +89,9 @@ export default async function Page({ params }: PageProps): Promise description: work.description ? work.description.replace(/<[^>]*>/g, '').slice(0, 160) : work.title, - image: work.image ? getImageUrl(work.image._id, 1200, 630) : undefined, + image: work.image + ? getImageUrl(work.image._id, 1200, 630, { mode: 'thumbnail', mime: 'jpeg' }) + : undefined, brand: { '@type': 'Organization', name: 'Иконописная Артель', diff --git a/src/app/works/page.tsx b/src/app/works/page.tsx index b8d8b7d2..d2dfc53d 100644 --- a/src/app/works/page.tsx +++ b/src/app/works/page.tsx @@ -55,7 +55,7 @@ export default async function Page({ title: work.title, description: work.description, href: `/works/${work.slug || work._id}`, - image: getImageUrl(work.image._id, 400, 400), + image: getImageUrl(work.image._id, 600, 500), alt: work.image.title || work.title, })) diff --git a/src/components/AboutPage/AboutPage.module.scss b/src/components/AboutPage/AboutPage.module.scss index f8df252a..7ba8d998 100644 --- a/src/components/AboutPage/AboutPage.module.scss +++ b/src/components/AboutPage/AboutPage.module.scss @@ -25,23 +25,3 @@ .about__text { font-size: 22px; } - -.about__slider { - overflow: hidden; -} - -.about__image-wrapper { - position: relative; - - width: 100%; - height: 500px; - - @media (max-width: $tablet-min-width) { - height: 400px; - } -} - -.about__image { - border-radius: 40px; - box-shadow: $cardShadow; -} diff --git a/src/components/AboutPage/AboutPage.tsx b/src/components/AboutPage/AboutPage.tsx index 0b04c045..309d5436 100644 --- a/src/components/AboutPage/AboutPage.tsx +++ b/src/components/AboutPage/AboutPage.tsx @@ -2,11 +2,10 @@ import { JSX } from 'react' import clsx from 'clsx' import { ImageItem, SlideItem } from '@/types/types' import { createSanitizedHTML } from '@/functions/functions' -import SliderDetail from '@/components/SliderDetail/SliderDetail' import aboutPageStyles from './AboutPage.module.scss' import { fetchSingleton, getImageUrl } from '@/lib/api-client' -import Image from 'next/image' import EmptySection from '@/components/EmptySection/EmptySection' +import GalleryBlock from '@/components/GalleryBlock/GalleryBlock' type AboutPageProps = { title: string @@ -25,11 +24,14 @@ export default async function AboutPage(): Promise { const slidesList: SlideItem[] = about.slider?.map((image) => ({ id: image._id, - image: getImageUrl(image._id, 800, 800), + image: getImageUrl(image._id, 800, 500), alt: image.title || about.title, })) const imageSrc = about.image ? getImageUrl(about.image._id, 800, 500) : '' + const imageFullSrc = about.image + ? getImageUrl(about.image._id, 1600, 1000, { mode: 'bestFit' }) + : '' const alt = about.image?.alt ?? title return ( @@ -44,31 +46,12 @@ export default async function AboutPage(): Promise { /> - {slidesList.length > 0 && ( -
- -
- )} - - {slidesList.length === 0 && imageSrc && ( -
- {alt} -
- )} + ) diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index ad483f5d..9b06b2c1 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -17,8 +17,8 @@ export default function Card({ data }: CardProps): JSX.Element { className={cardStyles['card__image']} src={data.image} alt={data.alt} - width={400} - height={400} + width={600} + height={500} />
diff --git a/src/components/Detail/Detail.module.scss b/src/components/Detail/Detail.module.scss index 2639b030..bedb7ef3 100644 --- a/src/components/Detail/Detail.module.scss +++ b/src/components/Detail/Detail.module.scss @@ -1,8 +1,5 @@ @use '../../styles/variables.scss' as *; -.detail { -} - .detail__container { display: grid; grid-template-columns: 1fr 1fr; @@ -25,23 +22,3 @@ .detail__text { font-size: 22px; } - -.detail__slider { - overflow: hidden; -} - -.detail__image-wrapper { - position: relative; - - width: 100%; - height: 500px; - - @media (max-width: $tablet-min-width) { - height: 400px; - } -} - -.detail__image { - border-radius: 40px; - box-shadow: $cardShadow; -} diff --git a/src/components/Detail/Detail.tsx b/src/components/Detail/Detail.tsx index 8fa4e7c0..6ed6e11f 100644 --- a/src/components/Detail/Detail.tsx +++ b/src/components/Detail/Detail.tsx @@ -2,10 +2,9 @@ import { JSX } from 'react' import clsx from 'clsx' import { ImageItem, SlideItem } from '@/types/types' import { createSanitizedHTML } from '@/functions/functions' -import SliderDetail from '@/components/SliderDetail/SliderDetail' import detailStyles from './Detail.module.scss' import { getImageUrl } from '@/lib/api-client' -import Image from 'next/image' +import GalleryBlock from '@/components/GalleryBlock/GalleryBlock' type DetailProps = { title: string @@ -21,6 +20,7 @@ export default function Detail({ description, }: DetailProps): JSX.Element { const src = getImageUrl(image._id, 800, 500) + const fullSrc = getImageUrl(image._id, 1600, 1000, { mode: 'bestFit' }) const alt = image.alt ?? title return ( @@ -35,27 +35,12 @@ export default function Detail({ />
- {slidesList.length > 0 && ( -
- -
- )} - - {slidesList.length === 0 && image && ( -
- {alt} -
- )} + ) diff --git a/src/components/Forms/FormCalculation/FormCalculationClient.tsx b/src/components/Forms/FormCalculation/FormCalculationClient.tsx index ad8daffd..e8b3dbb9 100644 --- a/src/components/Forms/FormCalculation/FormCalculationClient.tsx +++ b/src/components/Forms/FormCalculation/FormCalculationClient.tsx @@ -3,7 +3,8 @@ import { JSX, PointerEvent, KeyboardEvent, useEffect, useRef, useState, useMemo } from 'react' import formStyles from '../../../styles/modules/form.module.scss' import clsx from 'clsx' -import type { PriceItem } from '@/types/types' +import type { PriceItem, GoldTypeValue } from '@/types/types' +import { GOLD_TYPE_OPTIONS } from '@/const/const' type Props = { prices: PriceItem[] @@ -12,7 +13,7 @@ type Props = { export default function FormCalculationClient({ prices: initialPrices }: Props): JSX.Element { const prices: PriceItem[] = useMemo(() => initialPrices || [], [initialPrices]) const [selectedId, setSelectedId] = useState('') - const [goldType, setGoldType] = useState<'without_gold' | 'all' | 'halo'>('without_gold') + const [goldType, setGoldType] = useState('without_gold') const [calculated, setCalculated] = useState('') const isLoading = false const error: string | null = @@ -239,56 +240,25 @@ export default function FormCalculationClient({ prices: initialPrices }: Props): role="radiogroup" aria-label="Тип золочения" > - - - - - + {GOLD_TYPE_OPTIONS.map((opt, idx) => ( + + ))} handleBlur('name', evt.target.value)} /> @@ -221,7 +221,7 @@ export default function FormContactsClient({ agreementUrl, policyUrl }: Props): type="email" name="email" autoComplete="on" - placeholder="Email" + placeholder="Email *" required onBlur={(evt) => handleBlur('email', evt.target.value)} /> @@ -238,8 +238,10 @@ export default function FormContactsClient({ agreementUrl, policyUrl }: Props): className={formStyles['form__input']} name="message" rows={5} - placeholder="Ваше сообщение" + placeholder="Сообщение *" autoComplete="off" + required + onBlur={(evt) => handleBlur('message', evt.target.value)} > diff --git a/src/components/Forms/FormOrder/FormOrder.tsx b/src/components/Forms/FormOrder/FormOrder.tsx index 56b1ed2e..1b2c6a81 100644 --- a/src/components/Forms/FormOrder/FormOrder.tsx +++ b/src/components/Forms/FormOrder/FormOrder.tsx @@ -5,7 +5,7 @@ import type { CategoryFromServer, PriceItem } from '@/types/types' export default async function FormOrder(): Promise { const [categories, prices] = await Promise.all([ - fetchCollection('categories', { sort: { sort: 1 } }), + fetchCollection('category', { sort: { sort: 1 } }), fetchCollection('price', { sort: { sort: 1 } }), ]) diff --git a/src/components/Forms/FormOrder/FormOrderClient.tsx b/src/components/Forms/FormOrder/FormOrderClient.tsx index 9bf86041..0fc4661b 100644 --- a/src/components/Forms/FormOrder/FormOrderClient.tsx +++ b/src/components/Forms/FormOrder/FormOrderClient.tsx @@ -17,7 +17,8 @@ import { z } from 'zod' import formStyles from '../../../styles/modules/form.module.scss' import { submitApplication } from '@/actions/forms' import { applicationFormSchema, validateFormField } from '@/lib/schemas' -import type { CategoryFromServer, PriceItem } from '@/types/types' +import type { CategoryFromServer, PriceItem, GoldTypeValue } from '@/types/types' +import { GOLD_TYPE_OPTIONS } from '@/const/const' import DropZone, { DropZoneRef } from '@/components/DropZone/DropZone' type Props = { @@ -54,7 +55,8 @@ export default function FormOrderClient({ const sizeOptionsRef = useRef>([]) // Радио-переключатель типа золочения - const [goldType, setGoldType] = useState<'without_gold' | 'all' | 'halo'>('without_gold') + const [goldType, setGoldType] = useState('without_gold') + const goldTypeLabel = GOLD_TYPE_OPTIONS.find((o) => o.value === goldType)?.label ?? '' const radioSwitchRef = useRef(null) const radioLabelRefs = useRef>([]) const radioIndicatorRef = useRef(null) @@ -241,7 +243,6 @@ export default function FormOrderClient({ const formData = new FormData(formRef.current) formData.set('smart-token', token) - // Добавляем файлы из DropZone вручную const files = dropZoneRef.current?.getFiles() ?? [] files.forEach((file) => formData.append('photos', file)) @@ -406,7 +407,7 @@ export default function FormOrderClient({ <> - +

Вид иконы

@@ -541,13 +542,7 @@ export default function FormOrderClient({ role="radiogroup" aria-label="Тип золочения" > - {( - [ - { value: 'without_gold', label: 'Без золота' }, - { value: 'all', label: 'Золотой фон и нимб' }, - { value: 'halo', label: 'Только нимб' }, - ] as const - ).map((opt, idx) => ( + {GOLD_TYPE_OPTIONS.map((opt, idx) => (