add eslint, bug fixes

This commit is contained in:
Salam-vsem
2025-05-18 00:56:02 +03:00
parent bc645a8261
commit 33a432cbd1
18 changed files with 3264 additions and 32 deletions

4
.gitignore vendored
View File

@@ -22,3 +22,7 @@ pnpm-debug.log*
# jetbrains setting folder
.idea/
# ESLint
.eslintcache
.eslintrc.*.backup

49
eslint.config.js Normal file
View File

@@ -0,0 +1,49 @@
import eslintPluginAstro from 'eslint-plugin-astro'
import typescriptParser from '@typescript-eslint/parser'
import typescriptPlugin from '@typescript-eslint/eslint-plugin'
import reactPlugin from 'eslint-plugin-react'
export default [
...eslintPluginAstro.configs.recommended,
{
ignores: [
'node_modules/**',
'dist/**',
'.astro/**',
'coverage/**',
'*.config.{js,ts,mjs}',
'.prettierrc',
'package*.json',
'tsconfig.json',
],
files: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'],
plugins: {
'@typescript-eslint': typescriptPlugin,
react: reactPlugin,
},
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
},
},
rules: {
...typescriptPlugin.configs.recommended.rules,
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
},
},
]

3127
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,9 @@
"preview": "astro preview",
"astro": "astro",
"format": "prettier --write .",
"format:check": "prettier --check ."
"format:check": "prettier --check .",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.astro --cache --max-warnings 0",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx,.astro --fix --cache"
},
"dependencies": {
"@astrojs/react": "^4.2.7",
@@ -24,6 +26,11 @@
"tailwindcss": "^3.4.17"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.27.0",
"eslint-plugin-astro": "^1.3.1",
"eslint-plugin-react": "^7.37.5",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.11"

View File

@@ -16,6 +16,7 @@ const t = useTranslations(lang)
<footer id="contacts" class="flex flex-col items-center gap-4 text-center md:gap-6">
<script
script-src:unsafe-inline
is:inline
type="text/javascript"
charset="utf-8"

View File

@@ -4,6 +4,7 @@ import { SwiperSlide } from 'swiper/react'
import StarIconSrc from '@/assets/icons/StarIcon.svg'
import { useTranslations } from '@/i18n/utils'
import { SimpleSwiper } from './swiper/SimpleSwiper'
import { isAboveBreakpoint } from '@/libs/breakpoint'
type Review = z.infer<ReturnType<typeof reviewsSchema>>
type Author = Review['author']
@@ -18,6 +19,7 @@ type ReviewsProps = {
const REVIEWS_LINK =
'https://yandex.ru/maps/213/moscow/?ll=37.469982%2C55.781185&mode=poi&poi%5Bpoint%5D=37.469074%2C55.781319&poi%5Buri%5D=ymapsbm1%3A%2F%2Forg%3Foid%3D19715751392%26yclid%3D10576664134676381695%26banner_id%3D1873406736351323707&tab=reviews&z=17.75'
const swiperSlideClassName = '!h-auto md:max-w-[600px]'
const isMd = isAboveBreakpoint('MD')
export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
const t = useTranslations(lang)
@@ -25,11 +27,10 @@ export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
return (
<SimpleSwiper
slidesPerView={1}
freeMode={false}
freeMode={isMd}
breakpoints={{
768: {
slidesPerView: 'auto',
freeMode: true,
},
}}
>

View File

@@ -1,16 +1,14 @@
import { useTranslations } from '@/i18n/utils'
import type { CollectionEntry } from 'astro:content'
import rightArrowIconSrc from '@/assets/icons/RightArrowIcon.svg'
export type CoachCardProps = CollectionEntry<'coaches'> & { lang?: string }
export const CoachCard: React.FC<CoachCardProps> = ({ data, lang, slug }) => {
const { name, description, cover, coverAlt } = data
const t = useTranslations(lang)
export const CoachCard: React.FC<CoachCardProps> = ({ data, slug }) => {
const { name, cover, coverAlt } = data
const coachPageLink = `/coaches/${slug}`
return (
<div className="aspect-3/4 relative h-full max-w-[262px] overflow-hidden rounded-3xl text-white md:max-w-[320px] lg:max-w-[400px]">
<div className="relative aspect-3/4 h-full max-w-[262px] overflow-hidden rounded-3xl text-white md:max-w-[320px] lg:max-w-[400px]">
<img
{...cover}
loading="lazy"

View File

@@ -1,4 +1,4 @@
import type { CollectionEntry, z } from 'astro:content'
import type { CollectionEntry } from 'astro:content'
import { SimpleSwiper } from '../swiper/SimpleSwiper'
import { SwiperSlide } from 'swiper/react'
import { CoachCard } from './CoachCard'

View File

@@ -66,7 +66,7 @@ const MobileNavigation: React.FC<MobileNavigationProps> = ({ navItems }) => {
<AnimatedBurgerButton isOpen={isOpen} onClick={toggleMenu} />
<div
className={cn(
'fixed left-0 top-0 z-50 w-full h-[100dvh] transform bg-bg-main p-8 pt-16 transition-transform duration-300 ease-in-out',
'fixed left-0 top-0 z-50 h-[100dvh] w-full transform bg-bg-main p-8 pt-16 transition-transform duration-300 ease-in-out',
isOpen ? '-translate-x-0' : 'translate-x-full'
)}
>

View File

@@ -1,7 +1,7 @@
import type { servicesSchema } from '@/content.config'
import type { z } from 'astro:content'
import { useState } from 'react'
import { SwiperSlide } from 'swiper/react'
import { useCallback, useRef, useState } from 'react'
import { type SwiperRef, SwiperSlide } from 'swiper/react'
import { CharachteristicCard } from './CharachteristicCard'
import { cn } from '@/libs/cn'
import { SimpleSwiper } from '../swiper/SimpleSwiper'
@@ -11,9 +11,14 @@ export type ServicesProps = {
}
export const Services: React.FC<ServicesProps> = ({ services }) => {
const swiperRef = useRef<SwiperRef | null>(null)
const [serviceId, setServiceId] = useState(0)
const selectedService = services[serviceId]
const isSelected = (index: number) => index === serviceId
const updateServiceHandler = useCallback((index: number) => {
setServiceId(index)
swiperRef.current?.swiper.slideTo(0)
}, [])
return (
<div className="flex flex-col gap-5">
@@ -22,14 +27,14 @@ export const Services: React.FC<ServicesProps> = ({ services }) => {
<SwiperSlide style={{ width: 'auto' }} key={index}>
<button
className={cn('tag-btn', isSelected(index) && 'tag-btn-active')}
onClick={() => setServiceId(index)}
onClick={() => updateServiceHandler(index)}
>
{service.title}
</button>
</SwiperSlide>
))}
</SimpleSwiper>
<SimpleSwiper>
<SimpleSwiper ref={swiperRef}>
{selectedService.charachteristics?.map((charachteristic, index) => (
<SwiperSlide
className="aspect-3/4 !h-auto w-full max-w-[262px] md:max-w-[320px]"

View File

@@ -18,6 +18,7 @@ export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>(
ref={ref}
modules={[Navigation, Pagination, A11y, ...modules]}
slidesPerView={1}
spaceBetween={24}
pagination={{
clickable: true,
}}

View File

@@ -1,21 +1,26 @@
import { FreeMode, Mousewheel } from 'swiper/modules'
import { Swiper, type SwiperProps } from 'swiper/react'
import { Swiper, type SwiperProps, type SwiperRef } from 'swiper/react'
import 'swiper/css'
import 'swiper/css/free-mode'
import './simple-swiper.css'
import { cn } from '@/libs/cn'
import React from 'react'
export const SimpleSwiper: React.FC<SwiperProps> = ({ children, className, ...props }) => (
<Swiper
direction="horizontal"
slidesPerView="auto"
freeMode={true}
spaceBetween={24}
mousewheel={{ enabled: true, forceToAxis: true }}
modules={[FreeMode, Mousewheel]}
{...props}
className={cn('simple-swiper', className)}
>
{children}
</Swiper>
export const SimpleSwiper = React.forwardRef<SwiperRef, SwiperProps>(
({ children, className, ...props }, ref) => (
<Swiper
direction="horizontal"
slidesPerView="auto"
freeMode={true}
spaceBetween={24}
mousewheel={{ enabled: true, forceToAxis: true }}
modules={[FreeMode, Mousewheel]}
{...props}
ref={ref}
className={cn('simple-swiper', className)}
>
{children}
</Swiper>
)
)
SimpleSwiper.displayName = 'SimpleSwiper'

View File

@@ -1,4 +1,4 @@
import { defineCollection, reference, z, type ImageFunction } from 'astro:content'
import { defineCollection, z, type ImageFunction } from 'astro:content'
export const socialMediaLinkSchema = z.object({
icon: z.string(),

View File

@@ -8,6 +8,7 @@ export function getLangFromUrl(url: URL) {
if (lang in ui) return lang as keyof typeof ui
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return i18n!.defaultLocale
}

33
src/libs/breakpoint.ts Normal file
View File

@@ -0,0 +1,33 @@
import { TW_WIDTHS } from '@/constants/images'
type Breakpoint = keyof typeof TW_WIDTHS
export function getBreakpoint(): Breakpoint {
if (typeof window === 'undefined') return 'XXS'
const width = window.innerWidth
if (width >= TW_WIDTHS._2XL) return '_2XL'
if (width >= TW_WIDTHS.XL) return 'XL'
if (width >= TW_WIDTHS.LG) return 'LG'
if (width >= TW_WIDTHS.MD) return 'MD'
if (width >= TW_WIDTHS.SM) return 'SM'
if (width >= TW_WIDTHS.XS) return 'XS'
return 'XXS'
}
export function isAboveBreakpoint(size: Breakpoint): boolean {
const currentBreakpoint = getBreakpoint()
const breakpoints = Object.keys(TW_WIDTHS) as Breakpoint[]
const currentIndex = breakpoints.indexOf(currentBreakpoint)
const targetIndex = breakpoints.indexOf(size)
return currentIndex >= targetIndex
}
export function isBelowBreakpoint(size: Breakpoint): boolean {
const currentBreakpoint = getBreakpoint()
const breakpoints = Object.keys(TW_WIDTHS) as Breakpoint[]
const currentIndex = breakpoints.indexOf(currentBreakpoint)
const targetIndex = breakpoints.indexOf(size)
return currentIndex < targetIndex
}

View File

@@ -98,7 +98,7 @@ const t = useTranslations(lang)
{
optimizedReviews && (
<Section id="reviews" title={t('reviews.title')}>
<Reviews client:visible reviews={optimizedReviews} lang={lang} />
<Reviews client:only="react" reviews={optimizedReviews} lang={lang} />
</Section>
)
}

View File

@@ -77,7 +77,7 @@
@apply rounded-full border border-brand bg-transparent px-6 py-3 text-center text-text-light transition-colors duration-300 hover:bg-brand;
}
.tag-btn {
@apply text-sm hover:tag-btn-active rounded-full bg-card p-2 transition-colors duration-300;
@apply hover:tag-btn-active rounded-full bg-card p-2 text-sm transition-colors duration-300;
}
.tag-btn-active {
@apply bg-brand text-text-light;

View File

@@ -23,7 +23,7 @@ export default {
},
},
aspectRatio: {
'3/4': '3 / 4'
'3/4': '3 / 4',
},
keyframes: {
marquee: {